NiceOffer

八股文解析

限流算法:令牌桶和漏桶有什么区别?

限流高并发八股文

一句话结论

漏桶是恒定速率输出(强制平滑),令牌桶是允许突发流量(按需取令牌),前者重“整形”,后者重“限流”。

面试标准答法

1. 核心区别:整形 vs 限流

漏桶(Leaky Bucket):请求先进入一个固定容量的桶,桶底部以恒定速率漏水(即处理请求)。如果桶已满,新请求直接丢弃或排队。无论上游流量多猛,下游看到的永远是恒定速率。

令牌桶(Token Bucket):系统以固定速率向桶中放入令牌(Token),每个请求必须消耗一个令牌才能通过。桶有最大容量(burst size),允许瞬间积攒一批令牌,从而支持突发流量

一句话记忆:漏桶管“出口速率”,令牌桶管“入口速率+突发上限”。

2. 机制细节拆解

漏桶(Leaky Bucket)

  • 数据结构:FIFO 队列 + 定时器
  • 核心参数capacity(桶容量)、rate(漏水速率,即每秒处理请求数)
  • 工作流程
  1. 请求到达,尝试放入队列尾部
  2. 若队列已满(长度 ≥ capacity),拒绝(丢弃/返回 503)
  3. 定时器以 rate 频率从队列头部取出请求处理
  • 关键特性
  • 输出绝对平滑:无论输入多么不均匀,输出始终是匀速
  • 无突发能力:即使桶是空的,也不能加速处理
  • 实现简单:一个队列 + 一个定时器即可

令牌桶(Token Bucket)

  • 数据结构:计数器(当前令牌数)+ 时间戳(上次补充时间)
  • 核心参数capacity(桶容量,即最大突发量)、refill_rate(令牌补充速率)
  • 工作流程
  1. 系统以 refill_rate 向桶中添加令牌,直到桶满(容量为 capacity
  2. 请求到达时,检查桶中是否有令牌:
  • 有 → 取走一个令牌,请求通过
  • 无 → 拒绝或等待
  1. 懒更新(Lazy Refill):不依赖定时器,而是在请求到达时计算 (当前时间 - 上次补充时间) × refill_rate,一次性补充令牌
  • 关键特性
  • 支持突发:如果一段时间没有请求,桶会积满令牌,后续突发请求可一次性全部通过
  • 平均限流:长期来看,平均速率被限制在 refill_rate,但短期允许峰值
  • 实现高效:无定时器,纯计算,适合高并发场景

3. 对比表格

维度漏桶(Leaky Bucket)令牌桶(Token Bucket)
输出速率恒定,完全平滑允许突发,平均受限
突发处理不支持,桶满即拒支持,最多可突发 capacity 个请求
实现复杂度低(队列+定时器)中(计数器+时间戳计算)
适用场景网络流量整形(Traffic Shaping)、保护下游脆弱系统API 网关限流、突发业务场景(如秒杀)
典型参数capacity=1000, rate=100 req/scapacity=200, refill_rate=100 req/s
拒绝策略桶满直接丢弃令牌耗尽时等待或丢弃
内存占用O(capacity) 队列O(1) 计数器
代表实现Nginx limit_req(近似)Guava RateLimiter(SmoothBursty)、Redis+Lua

常见追问

追问回答要点
为什么很多生产系统用令牌桶而不是漏桶?① 支持突发流量,用户体验更好;② 无需定时器,纯计算实现,性能更高;③ 参数语义更直观(容量=突发量,速率=平均速率);④ 漏桶的恒定输出对某些场景(如数据库写入)虽然友好,但会浪费上游空闲期的处理能力
令牌桶如何实现“预热”(Warm-up)?使用 SmoothWarmUp 模式:初始令牌速率慢,逐步加速到目标速率。实现上通过“冷因子”(coldFactor)控制斜率,Guava 中默认 coldFactor=3,即从 1/3 速率开始爬升
Redis 实现令牌桶的原子性问题怎么解决?Lua 脚本 保证“取令牌+扣减”的原子性,或者用 Redis 4.0+ 的 RedLock 方案。核心是避免并发下超卖令牌。生产级方案:Redis + Lua(如 INCR + EXPIRE 实现滑动窗口限流)
漏桶的“恒定速率”真的恒定吗?在分布式环境下,如果多个实例各自实现漏桶,全局速率不恒定。需要引入 集中式协调(如 Redis + Lua 保证全局速率),但会引入网络延迟。这也是漏桶在分布式场景下不如令牌桶常用的原因之一

面试回答模板(30 秒版)

延伸准备

1. 滑动窗口 vs 令牌桶(加分点)

  • 滑动窗口(Sliding Window):按时间窗口计数,窗口内请求数超限则拒绝。优点是精确控制“每秒 N 次”,缺点是无法处理窗口边界突刺(如 59.9s 和 60.1s 各放 100 个)。
  • 令牌桶:天然平滑突发,但“平均速率”不够精确(如 100 req/s 的桶,可能在 1s 内放 200 个,随后 1s 内全拒)。
  • 加分表述:实际生产常组合使用——外层滑动窗口卡死峰值,内层令牌桶平滑流量。

2. 分布式限流的“一致性”问题

  • 单机令牌桶是纯内存操作,但分布式场景需要全局计数器。方案有:Redis + Lua(推荐,原子性)、Sentinel 集群流控(Token Server 模式)、Nginx + etcd 配置同步。
  • 关键挑战:网络开销(每次请求多一次 Redis RTT)、时钟漂移(如果依赖本地时间戳)、容灾(Redis 挂了怎么办——降级为本地限流或直接放行)。

3. 拒绝策略的工程化设计

  • 令牌桶/漏桶只解决“是否放行”,不解决“拒绝后怎么办”。生产级方案:
  • 排队等待(如 Kafka 削峰填谷)
  • 快速失败(返回 429 + Retry-After 头)
  • 降级兜底(返回默认值或走缓存)
  • 优先级队列(高优先级请求先取令牌——可扩展为多桶方案)

想系统备战大厂大模型/Agent 开发?NiceOffer 提供 SDE+LLM 双轨 1v1 陪跑,合同保底 40w 年薪,文末扫码咨询。