面试知识库
极高 进阶

限流方案设计#

一句话答案#

多层限流:前端→网关(Nginx 漏桶/Gateway 令牌桶)→服务(Sentinel 滑动窗口)→DB,分布式用 Redis+Lua。

核心要点

单机: Guava RateLimiter(令牌桶) 分布式: Redis+Lua(滑动窗口) / Sentinel(规则动态配置) 网关层: Nginx limit_req(漏桶) / Gateway RequestRateLimiter

多层限流: 前端→网关→服务→DB,层层防护

算法原理深挖#

令牌桶 vs 漏桶(最高频): 两者形状像,机制相反。

维度令牌桶 Token Bucket漏桶 Leaky Bucket
桶里装的令牌(拿到令牌才能放行)请求(请求排队等出水)
速率控制按固定速率往桶里加令牌按固定速率从桶里漏请求
突发流量允许(桶内积攒的令牌可一次性取完)不允许(出水恒速,强制整流)
空闲时攒令牌(攒满为桶容量)桶空,无积累
出口速率可瞬时超过平均速率恒定,绝不超过
  • 令牌桶为何容突发: 桶容量 = 攒令牌上限。系统空闲一段时间后桶里攒满令牌,此时突然来一批请求,能一次性把存量令牌取光,瞬时放行量 = 桶容量,超过平均速率——这正是”容忍突发”的本质。
  • 漏桶为何整流: 不管进水多猛,出水阀门恒速。突发流量只会让桶内排队变长(满了就丢),下游永远收到平滑的恒定速率。代价是即使下游有余力也不会加速,牺牲了突发吞吐换稳定
  • 惰性补令牌(lazy refill): 工程实现不开后台线程定时加令牌,而是每次请求来时按 (now - lastTime) × rate 一次性补算应新增的令牌数,再判断够不够扣。省线程、零误差。

滑动窗口消除临界突刺: 固定窗口的致命缺陷——窗口边界。设限 100次/分,若在 0:59 涌入 100 次、1:01 再涌入 100 次,跨越两个窗口但实际 2 秒内放行了 200 次,瞬时翻倍击穿。

  • 解法:把 1 分钟切成 N 个小格(如 6 格、每格 10 秒),窗口随时间向前滑动,统计「当前时刻往前推 1 分钟」覆盖到的所有格子计数之和。旧格子滑出窗口即不再计入,临界点不再有翻倍空档。格子越细,精度越高、内存开销越大。

Guava RateLimiter 预热(warmup): 令牌桶的变体 SmoothWarmingUp。系统冷启动时(缓存空、连接池未建立)直接打满速率会击垮下游,所以预热期内令牌发放速率从慢逐渐加速到稳态,给下游一个爬坡缓冲。底层用一个”梯形面积”模型计算冷启动期每个令牌的等待时间,平滑过渡,避免冷系统被瞬时打满。

Redis + Lua 滑动窗口(ZSET 实现): 分布式滑动窗口的标准答案。

  • 用一个 ZSET 存请求记录:member = 请求唯一 ID,score = 请求时间戳(毫秒)。
  • Lua 脚本里原子完成四步:① ZREMRANGEBYSCORE key 0 (now - window) 滑掉窗口外的过期记录;② ZCARD key 统计窗口内当前请求数;③ 若未超阈值则 ZADD key now reqId 记入本次;④ EXPIRE key 兜底防泄漏。
  • 为何要 Lua:判断 + 写入若分两条命令,并发下会有竞态;Lua 在 Redis 单线程里串行执行不被打断,保证「读计数-判断-写入」原子。
面试回答(2分钟版)

限流方案我会按多层防护来设计。前端层限制按钮点击频率,比如3秒内只能提交一次。网关层用Nginx的limit_req模块做漏桶限流,或者Spring Cloud Gateway配合Redis实现令牌桶,按接口维度控制QPS。服务层用Sentinel做细粒度限流,支持按接口、用户ID、IP三个维度设规则,滑动窗口算法统计请求数,规则可以通过Nacos动态配置不用重启。分布式场景下多个服务节点要统一计数,用Redis加Lua脚本实现原子的判断和计数操作,保证多节点限流准确。单机场景简单的话直接用Guava RateLimiter,底层是令牌桶算法,支持预热和平滑限流。限流触发后返回HTTP 429状态码和友好提示,不能直接丢弃请求不响应。设计时要注意限流阈值不能太严影响正常用户,建议先用监控跑一段时间拿到基线QPS再设定合理阈值。

追问与易错

追问方向:

  • “网关层和服务层限流区别?”→ 网关层做粗粒度全局限流(按 IP/接口维度,保护整个集群),服务层做细粒度业务限流(按用户/资源维度,保护具体服务);两层配合,网关挡住大部分恶意流量,服务层精细控制
  • “按 IP/用户/接口 分别限流?”→ IP 限流防爬虫/DDoS(单 IP 100次/分),用户限流防刷单(单用户 10次/分),接口限流保护核心资源(下单接口 5000 QPS);三个维度同时生效,任一触发即拒绝
  • “限流触发返回什么?”→ 返回 HTTP 429 Too Many Requests 状态码 + Retry-After 头告知客户端多久后重试 + 友好的 JSON 错误提示;不能直接丢弃不响应,否则客户端会超时重试反而加重压力

易错点:

  • ❌ 只在网关限流——服务层也需要
  • ❌ 限流越严越安全——太严影响正常用户