← 返回文章列表

限流算法怎么选:固定窗口 / 滑动窗口 / 令牌桶 / 漏桶

API网络避坑

四种算法对比

算法特点边界问题适用
固定窗口按整段时间计数窗口交界出现双倍突发简单统计
滑动窗口近似平滑实现稍复杂更均匀的限流
令牌桶允许一定突发需维护桶容量面向用户的 API
漏桶恒定速率流出突发会被排队或丢弃保护下游、削峰

分布式计数

多实例下要用共享存储:Redis 的 INCR + EXPIRE 或 Lua 脚本保证原子;否则每台机器各算各的,等于没限。

三个关键做法

  1. 返回 429 + Retry-After:让客户端知道何时重试,而不是盲目重试;
  2. 按身份维度限流:用户、API Key、租户分别计数,别只按 IP(NAT 下会误伤一群人);
  3. 配合退避抖动:客户端重试加指数退避和随机抖动,避免重试风暴把服务打挂。

和幂等、重试的关系

限流解决「别打太猛」,幂等解决「重试会不会重复生效」。两者配合,重试才安全。相关:API 幂等、重试与超时。

实战案例:三种限流引发的问题

  1. 固定窗口在边界被打爆:窗口切换瞬间两倍流量涌入。改用滑动窗口或令牌桶可平滑这一突刺。
  2. 只按 IP 限流:同一 NAT 出口后的正常用户被连带限流,攻击者换个 IP 即可绕过。应按用户、令牌或接口维度组合限流,IP 仅作兜底。
  3. 被限流却返回 500:客户端无法区分“稍后重试”与“服务故障”,重试风暴加剧拥堵。应返回 429 并带上 Retry-After。

常见问题(FAQ)

令牌桶与漏桶怎么选?令牌桶允许配置范围内的突发流量,漏桶输出更平滑;面向用户的接口通常用令牌桶。限流状态存哪?多实例必须用集中存储(如 Redis)共享计数,否则每个实例各限一份,实际放行量翻倍。要区分接口吗?要,登录、短信、导出等昂贵接口应单独设更严格的阈值。阈值怎么定?先压测得出单实例容量,再按用户与全局分别设定,并留出安全余量。

与业务策略的配合

限流不是孤立的中间件配置,它需要与业务规则配合才能真正有效。

  1. 分级而非一刀切:为不同接口设定不同阈值——登录与短信接口最严,查询类接口可宽松。用同一套阈值覆盖所有接口,要么误伤正常业务,要么对昂贵接口保护不足。
  2. 区分正常突发与异常扫描:允许用户短时间内连续操作的正常模式,同时对均匀高频、遍历式访问提高警惕。仅按次数限流会把正常用户和爬虫一起拦下。
  3. 给调用方明确反馈:除了返回状态码与重试时间,还应在响应头中给出剩余额度与重置时间,让客户端能自我调整,而不是靠反复试错。
  4. 限流要能临时调整:大促、活动或故障期间需要动态放宽或收紧。把阈值做成可热更新的配置,比改代码重新发布快得多。
  5. 防止绕过:同一用户的多个来源地址、多设备登录都应能被聚合到同一维度,否则攻击者只需切换地址即可绕过。

上线后的校验

限流上线后应验证三件事:正常用户的误伤率是否可接受、被限流的请求是否确实异常、以及限流本身是否引入了额外延迟。缺少校验的限流往往在真正发生攻击时才发现阈值形同虚设。

与计费、配额的衔接

限流之外,往往还需要配额管理:前者防止瞬时过载,后者控制总量并支撑计费。两者应基于同一套计量口径,否则会出现“限流没触发但配额已耗尽”这类难以向用户解释的情况。计量数据的采集精度与延迟也应与用户可见的用量保持一致。