速率限制与防滥用:令牌桶、滑动窗口与分布式限流

全面讲解微型博客的速率限制与防滥用:令牌桶/漏桶/滑动窗口算法、固定窗口 vs 滑动窗口、分布式限流(Redis 计数/令牌桶)、按用户/IP/接口维度限流、防爬虫与垃圾账号风控、限流响应(429 + Retry-After)与限流监控。

微型博客一旦上线,就面临「机器」的攻击:爬虫狂刷接口、脚本批量发帖、僵尸号刷粉。速率限制(Rate Limiting)是防滥用的第一道闸门——它不区分「好人坏人」,只限制「每个调用者的频率」。

本文系统讲解:限流算法、单机与分布式实现、维度设计、防爬虫风控、响应与监控。

一、为什么需要限流

1.1 滥用场景

- 爬虫:高频抓取时间线/用户数据
- 刷量:批量点赞/转发/关注
- 爆破:暴力尝试密码
- 刷帖:灌水/垃圾内容
- 资源耗尽:恶意高频调用消耗后端资源

1.2 限流的目标

- 保护后端资源(DB/Redis/带宽)
- 保护公平性(不让单用户霸占)
- 防控滥用(机器行为)
- 平滑流量(削峰)

一句话总结:限流是把「无限资源」变成「按调用者配额」——它先于业务逻辑拦截异常频率,是成本最低的防线。

二、限流算法

2.1 固定窗口

时间窗(如 1 分钟)内允许 N 次 → 窗口结束重置
  实现简单,但「窗口边界」有双倍突发问题
  例:0:59 到 1:01 各允许 100 → 实际 2 秒内可打 200

2.2 滑动窗口

用「最近 N 时间内」计数,无边界突发
  精确滑动:记录每个请求时间戳,剔除窗口外
  对数滑动:Redis ZSet + 时间戳,ZREMRANGEBYSCORE 清理

2.3 令牌桶(Token Bucket)

容量桶 + 按速率补充令牌,每请求消耗一个
  允许「突发」(桶满时可一次性消耗)但平均速率受限
  适合:允许短突发、整体受限的场景

2.4 漏桶(Leaky Bucket)

请求进桶,恒定速率流出 → 完全平滑,无突发
  适合:需要严格平流的场景

2.5 对比

算法突发能力实现适用
固定窗口边界突发最简单粗略限流
滑动窗口无突发中精确限流
令牌桶允许突发中通用推荐
漏桶无突发中平滑流

一句话总结:算法选型看「要不要突发」——令牌桶兼顾「平均速率 + 短突发」,是微型博客最常用的通用方案。

三、单机与分布式限流

3.1 单机限流(进程内)

每个服务实例自己计数(内存 / 本地缓存)
  优点:快、零网络
  缺点:多实例时各自独立 → 总配额 = 实例数 × 单机配额
  适用:单实例部署、或「每实例配额」可接受

3.2 分布式限流(Redis)

中心化计数:所有实例共享 Redis 计数
  保证全局限额精确

方案一:Redis INCR + EXPIRE(固定窗口)
  key = limit:{uid}:{window}
  INCR → 超过阈值则拒绝
  EXPIRE → 窗口过期自动重置

方案二:Redis ZSet(滑动窗口)
  key = limit:{uid}
  ZREMRANGEBYSCORE key 0 (now-window)
  ZCARD 计数 → 超过阈值拒绝 → ZADD
-- 固定窗口(Lua 脚本保证原子)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local count = redis.call('INCR', key)
if count == 1 then redis.call('EXPIRE', key, window) end
if count > limit then return 0 else return 1 end

3.3 高并发细节

- Redis 单点故障 → 限流失效(可降级为本地限流兜底)
- 计数 key 过多 → 内存压力 → 合理窗口与 key 策略
- Lua 脚本保证「检查-计数」原子性(防竞态)

一句话总结:分布式限流用 Redis 做中心计数,Lua 脚本保证原子——单机快、分布式准,生产常用「Redis 为主 + 本地兜底」。

四、限流维度设计

4.1 按什么限

维度针对典型限制
用户单用户行为发帖 10 次/分
IP匿名滥用未登录 100 次/分
设备单设备登录尝试 5 次/分
接口全局保护全局限流(防压垮)
对象单帖点赞同一帖 1 次

4.2 多层组合

请求进入 → IP 限流(第一层粗筛)
         → 用户限流(登录用户精细配额)
         → 接口级限流(关键接口额外保护)
         → 业务级限制(如关注上限、发言冷却)

4.3 关键接口的限流示例

POST /api/posts(发帖)     → 用户 5 次/分
POST /api/comments(评论)  → 用户 10 次/分
POST /api/auth/login(登录)→ IP 5 次/分(防爆破)
GET  /api/timeline(时间线)→ 用户 30 次/分(防爬)
POST /api/follow(关注)    → 用户 20 次/分(防刷关注)

一句话总结:维度设计是「按风险分级」——未登录看 IP、登录看用户、关键接口额外加限,层叠起来才能既拦机器又不误伤真人。

五、防爬虫与垃圾账号风控

5.1 爬虫识别

- 特征:无 JS/无 cookie、固定 UA、高频、无鼠标行为
- 手段:UA 分析、验证码(异常时)、行为验证、频率异常检测
- 数据:限流日志、异常流量告警

5.2 垃圾账号风控

- 注册风控:邮箱/IP 信誉、验证码、注册频率
- 行为风控:新号权重低、异常行为标记
- 图关联:同 IP/设备注册的账号群(刷粉团)
- 处置:标记、限流、封禁、申诉

5.3 验证码策略

- 正常用户:无感(不触发)
- 异常 IP/高频 → 滑块/图形验证码
- 重度滥用 → 账号锁定 + 人工审核

一句话总结:限流是「闸门」,风控是「稽查」——闸门拦频率、稽查判意图,两者配合才能区分「爬虫、僵尸、真人」。

六、限流响应与监控

6.1 标准响应

HTTP 429 Too Many Requests
  Retry-After: 30   (告诉客户端何时可重试)
  X-RateLimit-Limit: 100
  X-RateLimit-Remaining: 0
  X-RateLimit-Reset: 1750000000

客户端应尊重 429 + Retry-After,指数退避重试

6.2 错误码

// Go 中间件返回限流响应
func RateLimitMiddleware(limiter Limiter) gin.HandlerFunc {
    return func(c *gin.Context) {
        allowed, retryAfter := limiter.Allow(c)
        if !allowed {
            c.Header("Retry-After", strconv.Itoa(retryAfter))
            c.AbortWithStatusJSON(429, gin.H{
                "error": "rate limit exceeded, retry after " +
                    strconv.Itoa(retryAfter) + "s",
            })
            return
        }
        c.Next()
    }
}

6.3 监控指标

- 各接口限流触发率(触发 / 总请求)
- 被限流用户 / IP 分布
- 限流造成的 429 占比
- 滥用趋势(按时间/地区)

告警:单接口 429 占比突增 → 可能遭遇攻击

一句话总结:限流要「可观测」——429 + Retry-After 让客户端知进退,监控指标让运维看得见攻击正在发生。

七、总结

微型博客防滥用的体系可以概括为:先限流、再风控、后处置。算法上令牌桶平衡突发、滑动窗口保精确;实现上 Redis 分布式计数 + Lua 原子;维度上按用户/IP/接口分层;风控上识别爬虫与僵尸;响应上用 429 + Retry-After 规范客户端;监控上盯触发率与滥用趋势。

限流不是「拒绝用户」,而是「保护系统」——把频率闸门放对位置,把风控判据放准,把处置闭环打通,微型博客才能在真人与机器的洪流中保持可用、公平与安全。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「miniblog」更多文章

  1. 通知系统:通知类型、聚合去重、多端同步与推送架构
  2. 评论与互动系统:评论树、@提及、点赞转发与互动计数一致性
  3. 用户鉴权与会话:密码哈希、JWT vs Session、OAuth2 与刷新令牌