限流不是加个开关:令牌桶、漏桶和那几个容易误伤的细节

举报
yd_238022924 发表于 2026/11/03 00:54:32 2026/11/03
【摘要】 限流的目的不是防攻击而是保护自己。本文讲清令牌桶与漏桶的选择依据、固定窗口的临界突刺、原子性与单调时钟这两个硬要求,以及按地址限流误伤、拒绝时怎么给台阶这些实打实的坑。

限流不是加个开关:令牌桶、漏桶和那几个容易误伤的细节

限流几乎是每个对外接口都要做的一件事,但真正做对的不多。常见的两种极端是:要么只在前端加个按钮防连点,后端裸奔;要么拍一个"每秒 100 次"写死,结果大促当天把自己人拦在门外。

先明确一件事:限流的首要目的不是防攻击(那是 WAF 和风控的事),而是保护自己——让下游数据库、第三方接口、线程池不被瞬时流量打穿。想清楚这一点,参数怎么定就有了依据。

一、令牌桶和漏桶,差别在"允不允许突发"

两种算法的名字很形象,但很多解释绕了远路。一句话版本:

  • 令牌桶:桶里按固定速率攒令牌,请求来了就取一个。桶有容量上限,所以攒着的令牌可以一次性用掉——也就是允许一定程度的突发。
  • 漏桶:请求先进队列,出口按固定速率流出。出口速率绝对平滑,突发被削平,代价是排队延迟。

选哪个取决于业务:面对的是"用户手速偶尔快一下",用令牌桶;面对的是"下游只能承受恒定速率"(比如给第三方接口做代理),用漏桶。

绝大多数业务场景该选令牌桶,因为把偶发突发也拦掉,用户体验的损失远大于那点保护收益。

二、固定窗口的临界问题

最简单的实现是固定窗口计数:每分钟清零一次,计数到阈值就拒绝。它有个经典缺陷——窗口边界会放过两倍流量。

比如限制每分钟 100 次。某个客户端在 12:00:59 打满 100 次,12:01:00 计数清零后再打 100 次。这两百次请求实际集中在两秒内,但任何一分钟的窗口看都"没超限"。

解决办法有两个:

  1. 滑动窗口:按最近 60 秒滚动统计,边界问题消失,但要存每个请求的时间戳,内存成本高;
  2. 令牌桶:天然没有窗口概念,攒令牌的速度就是速率,边界问题不存在。

如果只是想低成本修掉临界突刺,还有一种折中:双窗口加权——用当前窗口和上一窗口的计数按时间比例加权,误差可控且只需存两个整数。

三、实现上的两个硬要求:原子性和单调时钟

自己实现计数器时,最容易踩的两个坑是:

第一,读改写不是原子的。 GET 拿到当前值、判断、再 INCR,三步之间会被并发插队,实际放过的请求数会明显超过阈值。单机要用原子操作或互斥锁;跨进程用 Redis 时,必须把"判断+自增"写进一段 Lua 脚本,靠 Redis 的单线程执行保证原子性。分段 GET 再 INCR 的写法在压测里能超限 30% 以上。

第二,别用系统时间做速率计算。 系统时间会被 NTP 校正,甚至被人手动改。前后两次取时间如果发生了回拨,算出来的间隔是负数,令牌会被一次性灌满。计算速率要用单调时钟(比如 elapsed 计时器那类),它只保证递增,不受校时影响。

四、分布式限流的三种粒度

  • 单机限流:够用,且没有网络开销。只要实例数固定、流量被负载均衡摊平,单机阈值 = 总量 / 实例数就行。缺点是扩容时阈值要跟着改。
  • 集中式限流:所有实例打同一个 Redis key,全局精确。代价是每次请求多一次网络往返,Redis 本身成了瓶颈和单点。
  • 混合式:全局配额按实例数切片,各实例本地限流 + 定期向中心申请/归还配额。复杂度高,但兼顾了准确性和性能。

大多数团队停在第一种就够了。真正需要全局精确的场景,往往说明配额本身就是业务资源(比如按租户卖的调用次数),那更该走"配额管理"而不是"限流"。

五、几个一定会误伤的细节

按来源地址限流会误伤 NAT 后面的所有人。 一个公司的出口可能是同一个地址,一个人刷爆,全公司被拦。同理,运营商的大规模地址转换会让"按地址"的语义变得很模糊。能给到用户维度的,就别用地址维度。

拒绝的时候要给客户端台阶。 直接返回一个笼统的错误码,客户端只能无脑重试,反而放大流量。标准做法是返回 429,并带上 Retry-After 告诉对方多久后再来;如果知道剩余配额,一并放在响应头里。

限流必须可观测。 至少要有三个指标:被限流的请求数、限流触发时的阈值、以及误伤率(被限流的用户里有多少是正常用户)。没有第三个指标,你永远不知道自己是不是拦错了人。

限流是最后一道,不是第一道。 前面该有的排队、降级、缓存、异步化都没做,只靠限流硬扛,等于把系统设计问题变成了"拒绝多少用户"的问题。

限流真正的难点从来不是算法,而是定阈值和选维度:拦谁、拦多少、拦了之后对方怎么办。这三个问题想清楚了,令牌桶那几十行代码反而是最简单的部分。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。