2. KMS限流基本原理:令牌桶 vs 漏桶归纳总结
说到KMS限流,大多数人第一反应就是「令牌桶」。嗯,这确实是最经典的算法。但我在实际排查过几次故障后发现,很多同学对它的理解其实停留在「知道名字」的程度。今日咱们就把两种算法彻底掰扯清楚,你想想看,以后遇到限流失效或抖动,心里就有底了。
2.1 令牌桶算法
先说说令牌桶。我习惯把它想象成一个蓄水池 + 定时投币机:系统以固定速率往桶里扔令牌,每个请求进来必须拿走一个令牌才能通过。如果桶空了,请求就被拒绝或排队。
这里有两个关键参数:rate(令牌生成速率)和capacity(桶容量)。
- rate:决定了平均能承受的请求频率。比如每秒50个令牌,意味着长期来看每秒最多处理50个请求。
- capacity:决定了瞬时突发处理能力。比如桶能存200个令牌,那么短时间可以一次性放走200个请求。
我之前遇到过一个线上事故:某个服务平时请求量很稳,结果活动开始时流量暴增3倍,限流却完全没有生效,cpu直接被打满。查到最后发现什么?桶容量设得太大,而且令牌生成速率和qps上限没拉开差距,桶一直有剩余令牌。说白了,突发处理能力被错误放大了。
capacity设为rate * 2到rate * 3。既能抗住瞬时的流量尖刺,又不会让桶长期处于满员状态。
2.2 漏桶算法
漏桶跟我们平时用的漏斗完全一样——水(请求)以任意速度往漏斗里倒,但底部出口以固定速率流出。如果进水太快,桶装满后会溢出,溢出的请求直接拒绝。
漏桶的核心思想是强制平滑,它不在乎你什么时候进来,反正出来是均匀的。这和令牌桶的本质区别:令牌桶允许短期波,漏桶完全削平波峰。
举个例子:你有一个API,客户要求调用频率不能超过20次/秒。用漏桶的话,哪怕在0.1秒内打进来50个请求,也只有前2个能在第1秒内出去(因为每秒20个),其余48个要么排队要么丢弃。而令牌桶呢?桶里有容量的话,这50个请求可以秒过,但后面就会饿死。
2.3 核心差异对比表
| 对比维度 | 令牌桶 | 漏桶 |
|---|---|---|
| 允许突发 | 是,上限取决于容量 | 否,严格平滑 |
| 对突发流量的容忍度 | 高 | 低 |
| 队列溢出策略 | 通常丢弃新令牌或拒绝请求 | 丢弃最早请求或新请求(需配置) |
| 适用场景 | API网关、微服务限流、允许一定峰值的业务 | 网络/磁盘IO限速、严格保证出口速率 |
| 实现难度 | 中等(需定时补充令牌) | 简单(计数器+队列) |
| 常用开源实现 | Guava RateLimiter, Redis+Lua | Nginx limit_req, Linux tc |
这个表我建议你截图保存。每次选型拿不准时,先看场景允许不允许突发。比如KMS接口,有时候客户端会突然批量重试,你想想是不是该用令牌桶?对。
2.4 选型心法
我做过的KMS限流项目里,最终全用了令牌桶。不是漏桶不好,而是业务场景天然需要容忍突发:客户端拿到初始时要批量请求,日常则是平稳流量。漏桶会把初始批量请求卡死在队列里,导致用户等半天。当然,如果你是做日志上传或CDN节点出口限速,那漏桶反而是首选。
这里有个简单的决策流程:
- 先问:业务是否允许短期流量尖峰?是→令牌桶;否→漏桶。
- 再问:对请求延迟有多敏感?如果允许排队,两者都可;如果担心排队导致超时积累,令牌桶更合适。
- 最后问:后端资源是否能承担突发?如果数据库或下游服务扛不住突发,那你必须用漏桶强行削峰。
2.5 流程图:令牌桶 vs 漏桶核心逻辑
下面是两种算法的执行对比图。请注意左侧令牌桶分「正常请求」和「突发请求」两条路径,右侧漏桶只有一个出口速率,所有请求必须排队。
2.6 避坑指南
最后分享几个我亲眼见过的坑:
- 桶容量设为0:有的同学误以为容量=0就能禁止所有突发,结果桶一直空着,任何请求都过不了。容量至少为1,否则永远拒接。
- 令牌补充频率太低:比如rate=100/s,但每10s补充一次,每次补充1000个。这会导致前9.9秒桶是空的,第10秒突然涌出1000个请求,限流形同虚设。应该均匀补充。
- 漏桶队列无限大:我见过一个同学用无界队列实现漏桶,结果请求堆积到内存溢出。漏桶一定要设上限,而且要考虑丢弃策略。
- 不区分用户:全局限流容易误伤所有用户。建议对每个调用方(appId/ak)分别维护独立的令牌桶。
好了,这一章的核心就这些。你只要记住:令牌桶适合业务限流,漏桶适合出口限速。一旦选错,调参也救不回来。