Token Bucket 解决的是一个很实际的问题:系统要限制长期吞吐,但不该拒绝每一次短时突发。

模型只有三个量:

  • refill rate:token 生成速度
  • capacity:桶最多能存多少 token
  • request cost:一次请求消耗多少 token

请求到来时,桶里有足够的 token 就放行并扣除;不够则拒绝、排队或降级。

一个例子

假设配置如下:

refill rate: 10 tokens / second
capacity: 100 tokens
request cost: 1 token

系统长期只能以每秒 10 个请求的速度补充额度。不过,空闲时可以攒下最多 100 个 token。因此下一批流量到来时,系统能立即接住 100 个请求,再以每秒 10 个的速度恢复。

这正是 Token Bucket 的价值。它允许系统把空闲期的额度留给下一次 burst。真实流量很少是一条平线。用户点击、定时任务和消息消费都会在短时间内集中到达。

实现

不需要开一个后台线程,每秒往桶里塞 token。更常见的是 lazy refill:请求到来时,根据距离上次 refill 的时间计算新增额度。

capacity = 100
tokens = 100
refill_rate = 10  # tokens per second
last_refill_time = now()

def allow_request():
    global tokens, last_refill_time

    current = now()
    elapsed = current - last_refill_time

    tokens = min(capacity, tokens + elapsed * refill_rate)
    last_refill_time = current

    if tokens >= 1:
        tokens -= 1
        return True

    return False

生产实现还要处理并发更新和时钟精度。多实例部署时,桶放在单机内存、Redis 还是网关层,也会直接改变一致性和成本。这些是系统边界问题,不是算法本身能替你决定的。

Token 用完以后

Token 不够,只表示请求现在不能通过。怎么处理取决于业务。

同步 API 通常直接返回 429 Too Many Requests,并用 Retry-After 告诉客户端何时再试。客户端应做退避,避免所有请求在同一时刻重放。

后台任务可以排队,但队列必须有上限和最大等待时间。否则限流器保护了下游,却让自己先耗尽内存。

非关键请求还可以降级。例如返回缓存、跳过附加计算,或只保留主链路。

我一般按请求性质来选:用户同步请求尽快失败;后台任务允许有限等待;非关键逻辑优先降级。

适用场景

API 网关、登录防刷、短信发送、消息消费和第三方 API 保护都适合 Token Bucket。它们有同一个约束:平均速率要稳,短时流量又不可能完全均匀。

所以 Token Bucket 限制的不是「某一秒最多多少请求」。它控制长期速率,同时明确给系统留出一段 burst capacity。