限流令牌桶

为什么需要

正常的使用本来就是一阵一阵的。在百家菜(本课程虚构的菜谱网站)的 App 里打开一道菜谱,手机会同时要正文、照片、评论,然后读者看上好一会儿,什么也不发。短时间里集中到来的一批,叫作(burst)。上一页的不管读者之前安静了多久。我们希望:长期平均每 12 秒不超过 8 个,安静过一阵的读者可以一下子多用几个。

怎样做

(token bucket)就是这样设计的。每个请求者有一个桶,桶里的令牌按固定的速度补充,本页默认每 1.5 秒补 1 枚,也就是每 12 秒 8 枚;桶最多装 8 枚,满了就不再补。每个请求要拿走 1 枚令牌才能放行,不够就拒绝。桶一开始是满的,安静一阵以后又会补满,于是一小批请求可以一下子全部放行。

「令牌」只是(负责计数、决定放行还是拒绝的组件)记下的一个数,没有东西真的被发出去。限流器也不必每 1.5 秒补一次:它只记桶里还有几枚、上次是什么时刻,请求到来时按过去了多久补算,再照常读出、判断、写回。补算是连续的,桶里常有零头,例如 4.2 枚;一个请求要整整 1 枚才能放行,不足 1 枚不够用。

不同的请求耗费的力气不同。「导出我收藏的全部菜谱」比「打开一道菜谱」费力得多,可以让它一次拿走 3 枚。这种按耗费记分的做法叫。代码网站 GitHub 的主要上限按次数计;另有一条每分钟的附加限制按点数计,大多数读数据的请求记 1 点,大多数改数据的请求记 5 点。它的(网站对外公开的一份请求清单,写明程序可以向它要什么)里,有些请求的点数没有公开(文档未注明日期)。

玩过手机游戏的人会觉得,令牌桶很像「体力值」:随时间恢复,有上限,每做一个动作扣一点。这个比喻有一处不成立:游戏里每人只有一份体力,限流器却要为成千上万个请求者各记一份。

令牌桶:攒下的令牌让一小批请求一下子放行

先看这里先看左边的桶。按「直接跳到第 19 秒」:第 12 秒起的一阵请求先用掉攒满的 8 枚令牌;桶空了以后,只有新补上的令牌能放行请求,第 17.5 秒和第 18.5 秒的两个被拒绝。再看读数「任意 12 秒里最多放行」:12 个,比 8 个多。

发请求的方式
设置
耗费大的请求
令牌桶尚未开始

流入每 1.5 秒补 1 枚,满了就不再补

流出每个请求拿走 1 枚

现在8 枚,最多 8 枚

开始时桶是满的。

用左右方向键前后移动时间,Home 回到开头,End 跳到最后。

  • 放行 0 个
  • 拒绝 0 个
  • 尚未处理 19 个

已选:请求 1 · 第 1 秒 · 尚未处理

用左右方向键选择一项。

现在:尚未开始

到此刻为止放行0个
到此刻为止拒绝0个
到此刻为止,任意 12 秒里最多放行0个
长期平均每 12 秒 8 个

代价是什么

令牌桶有意放宽了第 2 页那句保证。补充速度决定长期的平均速度,容量决定一次最多放行几个:长期平均仍不超过每 12 秒 8 个,可在某一段 12 秒里,攒满的 8 枚加上这段时间新补的,放行的最多可以接近两倍。图下的读数「任意 12 秒里最多放行」显示的就是这一点。容量越大,越宽容突发,服务器在那一刻承受的压力也越大,亚马逊云服务(AWS)的一篇工程文章称之为双刃剑(2020)。

令牌桶或与它等价的办法用得不少。在线支付公司 Stripe 的限流器用令牌桶(2017);人工智能公司 Anthropic 也用令牌桶,配额持续补充,不在固定的时刻清零。它的文档还提醒:「每分钟 60 个」可能按「每秒 1 个」执行,短时间里集中发出的请求仍可能被拒绝(文档未注明日期)。

资料来源

以下资料均于 2026-10-19 访问。

  1. Rate limits for the REST APIGitHub 文档,未注明日期
  2. Fairness in multi-tenant systemsDavid Yanacek,AWS Builders' Library,2020-11-13
  3. Scaling your API with rate limitersPaul Tarjan,Stripe,2017-03-30
  4. Rate limitsAnthropic(Claude Platform 文档),未注明日期