什么是令牌桶算法?工作原理是什么?使用它有哪些优点和注意事项?
- 标签:后端、限流算法
🏛 业务架构师
令牌桶(Token Bucket)是最常用的限流算法之一:以固定速率向桶里放令牌,桶有容量上限;请求到达时取一个令牌——取到则通过,桶空则拒绝/等待。
工作原理(两个参数定义行为):
- 速率:每秒放入多少令牌 = 允许的平均 QPS;
- 桶容量:桶里最多囤多少令牌 = 允许的突发量。
例:速率 100/s、桶容量 200——平时限 100 QPS;但流量突增至 250 QPS 时,囤积的令牌能先放行一批突发,超出部分才开始拒绝。
优点:
- 允许合理突发:真实业务流量天然有毛刺(整点活动、重试集中),漏桶的严格匀速会误杀正常突发,令牌桶平时限制、峰时容忍,更贴合业务形态;
- 支持积攒额度:低频期攒下的令牌可应对短时高峰;
- 实现简单、分布式友好(Redis + Lua 原子操作实现集群级令牌桶,Guava RateLimiter 实现单机版)。
注意事项(架构师的风险清单):
- 桶容量是双刃剑:容量给太大,突发直穿下游(数据库被瞬间打挂)——容量按下游真实承受力定,不是拍脑袋;
- 冷启动问题:空桶启动瞬间低通过率,可用预填充缓解(对应 Sentinel 的 Warm Up 模式思想);
- 集群一致性与时钟:Redis 实现依赖时钟,注意集群时间同步与 Redis 故障时的降级策略(Redis 挂了,限流要退化成单机限流而不是全放开);
- 被限流后的体验设计:429 响应、排队、友好文案——拒绝策略是业务决策。
一句话:令牌桶 = 平均限速 + 突发容忍,参数设定永远以下游真实容量压测数据为准。
🏗 系统架构师
机制:桶容量 B,以速率 r 恒速往桶里放令牌,桶满丢弃;每个请求取走一个令牌,无令牌则拒绝或等待。长期平均速率等于 r,瞬时突发上限为 B。
与漏桶对比:漏桶恒定速率流出(绝对平滑、拒绝突发);令牌桶允许受控突发(先攒后用)。真实业务流量天然突发(开抢、批量重试),令牌桶对突发友好,是主流默认(Guava RateLimiter、网关 Redis 加 Lua、Sentinel 排队模式的近似)。
优点:参数语义清晰(r 定吞吐、B 定突发容忍)、允许平均速率约束下的弹性、实现简单可分布式化。
注意事项(工程实践):
- B 与 r 的容量含义:B 应覆盖下游可承受的瞬时脉冲——如 DB 容量 5000 QPS,配 r=4500 加适当余量;突发放行过量会打穿下游缓存与连接池;
- 重试风暴放大:失败重试叠加令牌桶突发,有效压力乘重试次数——重试必须带退避(backoff 加 jitter)并计入限流预算;
- 分布式一致性:Redis 加 Lua 保证取令牌原子性,但 Redis 延迟成为路径依赖——高并发可用本地预取令牌摊薄;
- 多租户共享桶要做 per-key 公平,防止大租户吃光令牌。
💻 全栈开发专家
以恒定速率往桶里放令牌,桶有容量上限;请求到来时取一个令牌,桶空拿不到就拒绝或等待。两大参数:rate(生成速率 = 长期平均通过速率)、burst/容量(允许的瞬时突发上限)。
工作特性:
- 允许突发:空闲期令牌攒满桶,突刺流量最多放行"桶容量"个,随后被速率削平——比漏桶(恒速出水)更贴合"偶尔突发是正常"的互联网流量
- 长期平滑:长期通过率恒等于生成速率,不会超发
优点:兼顾平均速率控制 + 突发容忍,实现简单(计数器 + 时间戳补偿),适合 API/网关限流。
注意事项:
- 参数配合业务定:rate = 压测容量,burst = 可接受瞬时峰值(太小正常抖动也被限,太大失去保护意义)
- 单机令牌桶在分布式下各算各的,总量失控——集群限流要集中式实现:Redis + Lua 原子化"补发令牌 + 扣减",或 Gateway 的
RequestRateLimiter(Redis 令牌桶)、Sentinel 集群流控 - 与漏桶的区别:漏桶强制恒速输出(绝对平滑),令牌桶允许突发按需输出;保护脆弱下游(DB)用漏桶思路更稳
Gateway 实战:
yaml
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100 # 每秒补 100 个令牌
redis-rate-limiter.burstCapacity: 200 # 桶容量 200,允许 2 倍瞬时突发超量请求默认返回 HTTP 429。