微型博客一旦上线,就面临「机器」的攻击:爬虫狂刷接口、脚本批量发帖、僵尸号刷粉。速率限制(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 规范客户端;监控上盯触发率与滥用趋势。
限流不是「拒绝用户」,而是「保护系统」——把频率闸门放对位置,把风控判据放准,把处置闭环打通,微型博客才能在真人与机器的洪流中保持可用、公平与安全。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。