基于 Redis 的限流算法有哪些?

考查重点:流量窗口、突发与原子计数。固定窗口计周期次数,滑窗限制连续区间,令牌桶允许积攒突发,漏桶平滑输出;Redis 原子更新共享状态

本题目录

参考答案

核心回答

常见的是固定窗口、滑动窗口、令牌桶和漏桶。Redis 的作用是让多个服务实例共享限流状态;具体选哪种算法,要看业务要求的是“一个周期内最多多少次”“任意连续一段时间内最多多少次”,还是“允许短时突发、控制长期速率”。

固定窗口按固定周期计数;滑动窗口持续淘汰窗口外记录;令牌桶按速率补充额度并允许积累;漏桶通过受限排队或虚拟排队平滑接纳。实现时要让检查与更新保持原子,统一窗口和计数口径,并设计超限与 Redis 故障时的处理。

四类算法的机制与取舍

  • 固定窗口:按用户、接口和时间窗口建计数器,请求到来时增加计数,超过阈值就拒绝。通常用 String 计数并设置过期时间,状态少、实现简单。但相邻窗口交界处可能集中放行两批请求,例如每分钟限 100 次,前一分钟末尾和下一分钟开头各放行 100 次,短时间内就可能涌入 200 次。它不能保证任意连续一分钟都不超过 100 次。
  • 滑动窗口:精确做法是用 ZSET 保存窗口内已放行请求的时间戳,每次清理窗口外记录,统计剩余数量,再决定是否加入本次请求。这样能限制连续时间区间内的请求数,但每个请求都要留记录,内存和清理成本更高。也可以用分桶计数或相邻窗口加权来减少状态,这类方法存在粒度或估算误差,要与精确滑动日志区分。
  • 令牌桶:以速率 r 补充令牌,最多积累 B 个,请求消耗令牌。桶里有积累时可以立即放行一批请求,适合允许短时突发、控制长期平均速率的接口。Redis 中可以记录剩余令牌和上次补充时间,请求到来时按经过时间计算补充量,不必真的每隔一小段时间启动任务往桶里放令牌。
  • 漏桶:经典做法是请求进入有界队列,以较稳定的速率取出执行,队列满了就拒绝。它适合平滑下游流量,代价是增加排队延迟,还要限制最长等待时间。有些实现只用虚拟排队时间判断是否接纳,并不真的保存任务;如果需要延迟执行,仍然要有实际的调度与任务处理机制。

简单的周期配额可以用固定窗口;需要精确滚动区间限制可以选滑动日志;允许突发的 API 常适合令牌桶;下游需要平滑处理时可以考虑漏桶。阈值还要结合下游容量和请求成本确定。

Redis 实现要注意什么

把“判断是否超限”和“修改限流状态”做成原子操作。 只说 INCR 是原子的还不够:先读取、在应用里判断、再增加计数,并发请求仍可能一起通过。计数成功后单独设置 TTL,也可能在客户端中断后留下没有过期时间的键。通常用一段短 Lua 脚本,把读状态、判断、更新和必要的过期设置放在同一次执行里。

滑动日志同样要把清理、计数和条件写入放在一起。ZSET 的成员应包含唯一请求标识,不能只用时间戳当成员,否则同一时刻的请求可能互相覆盖。时间单位和窗口边界要统一;使用 Redis 的时间可以减少不同应用实例时钟不一致的问题,仍要考虑系统时间调整。

TTL 的用途是清理不再影响限流结果的状态,具体设置取决于算法。按自然分钟分窗,与“第一次请求后开始计时一分钟”是两种窗口定义;如果每次请求都重置计数器的过期时间,持续流量下还可能让窗口一直不结束。令牌桶则要保留足以正确计算补充量的状态,不能随意过期后重新送出一桶令牌。

Lua 执行期间会阻塞其他命令,因此脚本应保持短小,避免大范围扫描。多个 key 的方案还要考虑 Redis Cluster 的同槽约束。一个租户的全局计数器也可能成为热点,拆分配额能缓解热点,但会改变配额分配和精度,不能无条件当作等价优化。

超限和故障怎么处理

先定义限流维度,例如“租户+用户+接口”或“租户+模型”,身份应来自可信的认证结果。再明确计数口径:统计所有尝试、已放行请求,还是成功完成的业务动作。成功次数与请求次数不同,失败是否退还额度也需要额外规则。

超限后可以返回明确的限流响应,例如 HTTP 429,并给出合理的重试时间;也可以进入有界队列或降级服务。不能无限排队,也不能让客户端立刻密集重试。Redis 请求超时时,状态可能已经更新,盲目重试还可能重复消耗配额。

Redis 不可用时也要有约定:严格保护下游的接口可以拒绝请求;优先保证可用性的业务可以退回本地限流或短时有限放行,但此时需要接受全局额度控制变弱。Lua 的原子性也不等于故障切换后仍能保证严格的全局配额。

例如 Agent 调用模型接口,“每分钟请求数”“同时执行的请求数”和“Token 消耗预算”是三种约束。请求数可以用上述速率算法控制,并发数需要单独管理在途任务,Token 预算还要考虑预估、实际用量与结算,不能只靠一个 QPS 计数器解决。

常见追问

有 Redis 了,还需要本地限流吗?

可以需要。本地限流能快速挡住单实例突发,减少访问 Redis 的压力;Redis 再负责共享配额。两层阈值要一起设计,否则某个实例可能过早拒绝,而其他实例仍有余量。

滑动窗口一定比固定窗口好吗?

精确滑动日志解决了固定窗口的边界集中放行问题,但需要保存窗口内记录。低成本的分桶或加权计数则牺牲部分精度。业务如果只要求自然分钟配额,固定窗口可能已经足够。

参考资料

  • 国内:JavaGuide:限流详解:借鉴四类算法与分布式限流的说明,组织正文的放行规则比较,并解释 Lua 共享状态及 Redis 故障时的原子性、可用性取舍。
  • 国内:advanced-java:如何限流:采用计数窗口交界的突发例子和两种桶算法的解释,区分正文中的周期配额、平滑流量与允许突发。
  • 国内:阿里云 MSE:集群 Key 限流插件:多个网关共享 Redis、按请求属性构造限流维度的示例,说明全局规则如何落到具体用户和接口,并怎样返回超限响应。
  • 国外:Redis:INCR:Rate limiter 章节对比两种计数窗口,并分析 INCR 与 EXPIRE 分开执行的竞态,说明窗口定义和过期设置为什么需要一起设计。
  • 国外:Redis:Scripting with Lua:脚本原子执行及其阻塞服务器的约定,说明为什么可以将限流判断与更新放在一起,以及脚本为何应短小并明确访问的键。

相关面经

4 篇

小红书

Product Engineer(AI 与全栈方向)
一面
查看原面经 →
追问
知道哪些限流策略?

腾讯

测试开发工程师
实习一面
查看原面经 →
Redis做限流的常见算法有哪些(答固定窗口,滑动窗口,漏桶,令牌桶)

字节跳动

AI 应用开发工程师
日常实习一面
查看原面经 →
项目追问
项目中的限流是怎么实现的?

阿里巴巴

AI 应用开发工程师
暑期实习二面
查看原面经 →
用秒杀场景类似的思路,对Agent的很多接口调用,做一个接口限流

收录题集

1 个来源,共1 份题集

数据库高频知识点

Rocky Ding · AIGC-Interview-Book · 1 条原题

查看题集 →

相关题目

4 题