2026-11-02·by Sijie Wang#fundamentals#rate-limiting

rate-limiting

限流:四种算法,两种哲学

限流要在一串请求里决定:哪些放行、哪些等、哪些拒——让某种资源(CPU、下游服务、配额、人的收件箱)以它能承受的速率被消耗。每个设计要回答三个问题:按什么计数(按 IP、按用户、按 API key、按收件人——计数键决定谁共用一份预算)、预算怎么回充、超出的怎么办。

固定窗口。 在离散的时间桶里计数(这一分钟、这一小时)。超限即拒。实现最便宜(每个键、每个桶一个计数器),也最易推理。它公认的洞在边界:客户端可以踩着一个窗口的尾巴打满配额、再踩着下一个窗口的头再打满,瞬时速率翻倍。

滑动窗口。 按"到此刻为止的滚动时长"计数,没有固定边界可踩。两种实现:存时间戳日志(精确,但内存随流量涨),或加权估算——上一个窗口的计数按其与滚动窗口的重叠比例加权,再加上当前窗口的计数(近似,常量内存)。滑动窗口就是把边界洞堵上的固定窗口,代价是内存或精确度二选一。

漏桶。 请求进队列,队列以恒定速率往外漏。超出的不拒,而是延后(到队列满为止)。无论入流多突发,出流绝对平滑。这是整形(shaping):保护那些被突发本身伤到的东西——脆弱的下游、有严格 QPS 上限的第三方 API。

令牌桶。 桶里最多存 N 个令牌,以恒定速率回充;每个请求花令牌,够就放行。长期速率被回充速率封顶,但攒下的额度可以一次性花掉:桶深以内的突发即时通过。这是带突发额度的准入——漏桶的对偶。(两者在计量上等价:一边的水位就是另一边容量减令牌余额,GCRA 的表述把这个等价写成了精确形式。行为差别在突发去哪:令牌桶让它当场通过,排队版漏桶把它换成等待。)

底下的两种哲学。 管制(policing)把超出的丢掉或拒绝(固定/滑动窗口、令牌桶)——调用方立刻知道,通常是一个带 Retry-After 的 429。整形(shaping)把超出的延后(漏桶、任务层 snooze)——什么都不拒,但由延迟吸收突发。选哪个取决于:调用方能不能利用一次拒绝(稍后重试、退避),还是拒绝会丢掉不可替代的东西(一封排队的邮件、一份已付费的任务)。

失败策略是设计的一部分,不是事后补丁。 限流器依赖一个计数存储;存储够不着时它必须二选一:fail-open(放行——可用性优先,适合公开读取面)或 fail-closed(全拒——防护优先,适合登录这类爆破目标)。没想过这一条的限流器,等于已经替你随机选了。

难的是分布式计数,不是算法。 请求落在许多台机器上时:集中计数精确,但每次请求多一个往返(还多一个依赖);各节点本地计数快,但只是对全局上限的近似(N 台 × 本地限额 ≠ 全局限额)。真实系统通常在小而关键的安全面上用精确计数,在大流量面上用近似。算法之间也会叠用:上面一层粗粒度的长窗口配额、下面一层细的短窗口或令牌桶——每层回答不同的问题:总量多少、以什么节奏、允许多大突发。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →