令牌桶限流:API 如何把流量洪峰变成可控波浪
令牌桶把长期平均速率和短时突发容量分开,是 API 限流中最常见的模型之一。本文从令牌补充、请求成本和 429 响应讲起,比较固定窗口、滑动窗口与漏桶,并拆解分布式限流的真实难点。

API 最怕的不是“有一个请求很慢”,而是某一秒突然涌进几十万请求。数据库连接池被占满,线程开始排队,重试又制造更多请求,最后一个本来健康的服务被拖成雪崩。令牌桶限流的做法很直观:先往桶里按固定速度放令牌,每个请求来时拿走一个;没有令牌,就等待、拒绝或降级。
令牌桶属于一类流量整形与速率控制算法。IETF 的相关规范也使用“令牌桶”描述可用容量、补充速率和到达流量之间的关系。RFC 2697;当 HTTP 服务拒绝过快的请求时,通常会返回 429 Too Many Requests,这个状态码定义在 RFC 6585。
桶里到底有什么
令牌桶至少有两个参数:
capacity:桶最多能存多少令牌,决定允许多大的瞬时突发。refillRate:每秒补充多少令牌,决定长期平均速率。
假设桶容量是 5,每秒补充 2 个令牌,每个请求消耗 1 个令牌:
- 长时间平均下来,最多放行约 2 个请求/秒。
- 如果桶之前攒满了,瞬间可以连续放行 5 个请求。
- 5 个令牌用完后,后续请求要等补充,或者直接收到 429。
这解释了它比“每秒固定只能两个请求”更灵活:正常情况下允许一小段突发,但不会让突发无限延长。
令牌桶的核心算法
每次请求到达时,系统先按照距离上次计算的时间补充令牌,再判断余额够不够。补充不能超过桶容量。
tokens = min(capacity,
tokens + (now - lastTime) * refillRate)
lastTime = now
if tokens >= requestCost:
tokens -= requestCost
allow()
else:
reject_or_wait()
实际代码要处理小数令牌、时间精度和并发更新。若一个请求消耗的资源差异很大,可以让读取接口消耗 1 个令牌、导出报表消耗 10 个令牌,而不是所有请求一视同仁。
它和其他限流算法有什么区别
固定窗口:简单,但窗口边界会放大突发
“每分钟最多 60 次”很容易实现,但用户在 12:00:59 发 60 次,12:01:00 再发 60 次,短时间内可能通过 120 次。固定窗口适合粗粒度保护,却不擅长平滑流量。
滑动窗口:更精确,但需要保存更多请求记录
滑动窗口会观察最近一段时间的请求,更公平,但高并发下需要维护计数或时间桶,存储和计算成本高于固定窗口。
漏桶:输出更平滑
漏桶更强调固定速度地处理流量,像一个底部开口大小固定的桶。它适合需要平滑输出的队列,但如果业务希望“攒一会儿后允许短突发”,令牌桶通常更贴合。
真正难的是“按谁限流”
算法本身不复杂,难点是限流键和状态放在哪里。常见限流维度包括用户 ID、API Key、IP、租户、路由和全局服务。只按 IP 限制可能误伤公司网络后的所有用户,只按用户限制又挡不住匿名攻击者;生产系统经常组合多个维度。
单机内存里的令牌桶只能保护一个进程。如果服务有多个实例,每个实例都允许 2 次/秒,集群总量可能变成实例数乘以 2。要得到全局上限,就需要共享计数状态或让流量先经过统一网关。共享状态又带来原子更新、网络延迟和故障降级问题。
一个常见实现会把 tokens 和 lastTime 放在 Redis 里,用 Lua 脚本一次完成补充和扣除,避免两个请求同时读到同一份余额。也可以按租户把请求路由到固定分片,减少共享状态,但这会牺牲部分弹性。
429 不是一句“太快了”就结束
服务返回 429 时,客户端需要知道如何恢复。响应可以附带 Retry-After,告诉客户端等待多久再试;客户端也应该使用指数退避和随机抖动,避免所有请求在同一时间再次冲回来。RFC 6585 对 429 和 Retry-After 的说明
服务端还要区分:
- 这是某个用户的配额耗尽,还是整个服务过载?
- 请求是可以排队,还是必须立即失败?
- 是否有缓存、只读副本或简化结果可以降级?
- 限流状态本身故障时,是偏向放行还是偏向拒绝?
“限流成功”不代表所有请求都被拒绝得很漂亮,而是系统在压力下仍然能保住核心路径,并给调用方一个可恢复的信号。
容易踩的坑
第一,时钟不一致。分布式实例用本地时间计算会产生边界误差,需要统一时间来源或接受一定误差。
第二,忽视请求成本。上传大文件和读取一个短字段都扣一个令牌,往往会让限流失去保护意义。
第三,只在应用代码里限流。请求已经占用了连接、TLS 和线程后才被拒绝,数据库可能仍然承受了压力。更早的网关、连接层和资源池保护通常更有效。
第四,把限流当成队列。令牌桶可以控制准入,但不自动解决任务执行时间;长任务仍然需要队列、超时、取消和幂等设计。
一句话带走
令牌桶的精髓是把“长期平均速率”和“短时突发容量”分开:桶的大小负责弹性,补充速度负责纪律。把它部署到正确的边界、配上明确的 429 与退避策略,才真正能把流量洪峰变成系统可以承受的波浪。



