接口限流怎么选:固定窗口、滑动窗口与令牌桶的落地对比
做后端接口,最怕的不是请求多,而是请求来得没有节奏。一次秒杀、一次重试风暴、一个失控的定时任务,都能把原本正常的服务打到超时。限流就是给接口装一道闸:超过承受能力的流量,先挡在门外,保住核心链路。
一、固定窗口:最简单,也最容易被打穿
固定窗口把时间切成一段一段,比如每分钟最多放行 1000 次。实现只需要一个计数器加一个过期时间,Redis 里一条 INCR 就能搞定。
问题出在窗口边界上。第 59 秒来了 1000 次,下一窗口的第 0 秒又来了 1000 次,两秒之内实际放行了 2000 次,等于限流在边界处失效了一半。流量越是集中,这个毛刺越明显。
适合:内部低频接口、后台管理类接口、对突发不敏感的场景。
二、滑动窗口:把边界磨平
滑动窗口把一个大窗口切成若干小格子,只统计最近一个窗口内的小格子之和。比如 1 分钟切成 6 个 10 秒格子,每次请求都看最近 6 格的总量。边界处的突刺被摊平了,曲线平滑很多。
代价是实现变复杂:要维护多个格子的计数,内存和计算都比固定窗口高一个量级。格子越细越平滑,成本也越高,工程上一般取 5 到 10 格就够了。
适合:对外 API、按量计费的接口、对平滑性有要求的场景。
三、令牌桶:允许突发,但守住平均速率
令牌桶以固定速率往桶里放令牌,桶满了就不再放;请求到来时取一枚令牌,没有令牌就拒绝。平时攒下的令牌可以在突发时一次性用掉,所以它允许短暂的突发,但长期平均速率被死死按住。
这正好匹配真实流量:大多数业务不是匀速的,而是平时平稳、偶尔尖峰。令牌桶既不误伤正常尖峰,又能挡住持续超载。
令牌桶的核心逻辑只有三步:第一步,每次请求先按流逝时间补充令牌,补充量等于流逝秒数乘以补充速率 rate,上限为桶容量 capacity;第二步,令牌数大于等于 1 时扣 1 枚并放行;第三步,令牌不足 1 枚时拒绝请求并返回 429。补充速率 rate 决定长期承载能力,桶容量 capacity 决定能容忍多大的突发,capacity 设得越大,突发越放得开,风险也越大。生产环境建议直接用成熟的限流组件实现,不要自己手写。
四、三种算法怎么选
- 只要“别超量”,实现越简单越好:固定窗口。
- 对外暴露、按量计费、怕边界毛刺:滑动窗口。
- 流量有尖峰、要保护下游数据库和第三方依赖:令牌桶。
- 分布式场景下,三种算法都不要在单机内存里做计数,用 Redis 集中计数,或者用网关层统一限流,否则每个节点各限各的,总量会失控。
五、落地时容易踩的坑
- 限流键选错:按用户限流还是按接口限流,粒度不同效果天差地别;一个大客户的突发不应该拖垮所有用户。
- 只限流不降级:被拒绝的请求要返回明确的 429 和重试建议,而不是让调用方无差别重试,否则重试本身会变成第二波洪峰。
- 阈值拍脑袋:先压测出单机和下游的真实承载能力,再倒推阈值;上线后按监控数据定期复核。
- 忽略预热:服务刚启动、缓存还是冷的时候,满速放行等于自杀,启动阶段可以适当收紧。
结语
限流没有银弹,只有取舍。固定窗口胜在简单,滑动窗口胜在平滑,令牌桶胜在兼顾突发与均值。先搞清楚自己的流量形态,再选算法、再定阈值,比纠结哪种算法“更高级”重要得多。
- 点赞
- 收藏
- 关注作者
评论(0)