Theory · Microservice · Rate Limiting

限流

容量有限而流量无限 —— 用算法把洪峰挡在容量之内,保护自己也被保护

一句话本质

对并发/速率设上限:超限的请求快速失败或排队,换取系统在容量内确定性地服务

四大算法

固定窗口 · 滑动窗口 · 漏桶(整流)· 令牌桶(许可突发)——单机与分布式实现分开记

工程纵深

网关限流(防刷粗控)→ 服务限流(保容量细控)→ 自适应限流(按负载动态调阈值,BBR/SRE 思想)

这份 deck 回答"流量洪峰怎么挡":先明确限流对象与维度,再逐个拆四大算法(配窗口对比图与桶图),然后是分布式限流的 Redis+Lua 实现与集群配额难点,最后是限流位置分层与自适应限流(BBR)。核心考点:固定窗口的临界突刺、令牌桶与漏桶的区别、Redis+Lua 的原子性。

Why Rate Limiting Matters

先看现场:容量是死的,流量是活的

一个能在脑子里跑起来的场景

时刻发生的事
平时订单服务压测容量 3000 QPS,日常 800 QPS,单次请求 20ms,一切平静。
10:00运营群发 push,20000 QPS 打进来——是容量的 6.6 倍
10:00:03线程池 / 连接池被打满,多出来的 17000 个请求在排队;延迟 20ms → 500ms → 5s。
10:00:10客户端超时 → 自动重试 → 流量变成 25000 QPS(重试放大)。用户等不及反复点,雪上加霜。
10:01服务彻底不可用——注意:不只是下单挂了,连原本正常的查询接口也一起挂了,因为资源被排队请求占满。
关键观察:系统不是"变慢"了,而是越过了容量拐点之后延迟雪崩式上升,最后所有人都服务不好。这是过载独有的恶性曲线——多进来的流量不只是自己失败,还会拖垮本来能成功的那些请求

没有限流 vs 有限流(同一场 push)

没有限流:20000 个请求全部涌进来,成功极少、全部超慢,最终 0 个可用服务。
装了限流:每秒只放行 3000 个,其余 17000 个立刻返回 429(不等、不排队)——3000 个请求仍是 20ms 返回,服务始终可用,被拒的客户端拿到 Retry-After 后错峰重试。
代价:17000 个用户被拒绝;收益:3000 个用户正常下单,且系统不会滚雪球到全站不可用。

限流换掉了什么

它换掉的是"来者不拒、谁也服务不好"的过载状态。核心交换是:用"一部分请求快速失败"换"剩余请求的服务质量确定"——注意"快速"两个字很关键,被拒的请求必须立刻返回,如果让它们排队等待,就等于把洪峰留在了系统里。

本 deck 的路线

先明确限什么、按什么限(第 3 页)→ 再看四种算法怎么把"速率"管住(第 4-6 页)→ 然后解决多台机器怎么协同限(第 7-8 页)→ 最后讲限在哪一层、被拒了怎么办(第 9-10 页)。读完你应该能回答:"这道闸该设在哪、设多少"。

动机页:以“容量 3000 / 突发 20000”的具体数字串起一条时间线,强调越过拐点后延迟雪崩 + 重试放大 + 正常接口被连带拖垮。对比“无限流 vs 有限流”的结果,指出限流的交换本质是“用快速失败换确定性”,并强调“快速”二字(排队等于把洪峰留在系统内)。

Why & What

为什么限流:限什么、按什么限

开场那场雪崩,解法就落在这一页的三个选择上:保护谁(护己 / 护依赖)· 按什么统计(限流 key)· 限哪种量(QPS / 并发)。

两个目的(方向相反)

  • 保护自己:把超出容量的流量挡在外面,拒绝一部分请求,保住剩余请求的服务质量——有损但确定
  • 保护依赖:作为调用方限制自己对下游的请求速率(配额/礼貌限流),不把下游打挂——对第三方 API 尤其重要
  • 不加限流的后果:容量打满后延迟雪崩式上升,所有人都服务不好(对比:限流后 80% 请求正常服务)

限流维度(key 的选择)

维度场景
全局总量保护服务总容量(QPS 上限)
按调用方/API Key开放平台配额、计费
按用户/设备 ID防单用户刷接口
按 IP网关防爬/防刷(注意 NAT 共出口误伤)
按接口/资源热点接口单独限额(秒杀 vs 查询)
按租户多租户 SLO 隔离(防邻居效应)

限的是什么量

QPS(速率)、并发数(同时在处理的请求)、连接数、带宽——四种"量纲",先选量纲再选算法

阈值从哪来

压测得出容量拐点 → 乘安全系数(70-80%)→ 全链路压测验证 → 配置中心动态可调(大促调高/故障时调低)

与容错的关系

限流是"服务端主动防御"(进来的流量),熔断/隔离是"调用方自我保护"(出去的调用)——见容错 deck

限流的第一层不是算法而是设计:两个目的(保护自己、保护依赖)、六个维度(全局/调用方/用户/IP/接口/租户)、四种量纲(QPS/并发/连接/带宽)。阈值来源要讲压测拐点乘安全系数再动态可调。这页给面试官的是设计感,下一页开始才是算法。

Prerequisites & Glossary

先把词认全:下面每一页都会用到它们

术语一句话理解(先记住这个,细节后面展开)
限流
Rate Limiting
速率或并发设上限,超出的请求快速失败(或排队),让系统在容量内确定性地服务
QPS / 吞吐每秒处理的请求数。与并发的关系:QPS = 并发数 ÷ 平均耗时(Little's Law)
并发数同一时刻正在处理的请求数。限并发 = 限制"同时占用的资源数",比限 QPS 更贴近资源
容量 / 拐点系统能长期承受的最大吞吐;越过拐点后延迟会雪崩式上升(开场那张图)
限流 key
(维度)
按什么口径统计次数:全局 / 用户 / IP / 接口 / 租户。选错 key 等于限了个寂寞
窗口
Window
统计用的时间区间。固定=到点清零,滑动=区间随时间平移
令牌桶参数
r 与 b
r = 每秒投放的令牌数(长期平均速率);b = 桶容量,也就是允许的突发预算
突发
Burst
短时间内超过平均速率的合法流量(如整点秒杀)。能不能放突发,是令牌桶与漏桶的分水岭
整形 vs 管制
Shaping / Policing
整形=排队后再恒速发出(漏桶);管制=超限直接丢弃(令牌桶)。前者加延迟,后者直接拒绝
削峰填谷用队列把瞬时洪峰摊平到后面慢慢处理(异步任务适用;交互式请求不适合)

如果这些概念还不熟,先看这三篇

微服务总览 → 为什么一个系统会有很多实例(限流要跨实例协同的起因)
熔断降级容错 → 限流是"事前",熔断是"事中",降级是"善后"——三者是一套
Redis 单线程模型 → 后面分布式限流靠"Lua 脚本原子执行",根基在这里

本 deck 怎么用这些词

统一直觉:所有限流算法都在维护同一个东西——一个"额度计数器"。来一个请求扣一次,扣得到就放行,扣不到就拒绝。四种算法的差别只有一件事:额度怎么补充。固定窗口 = 到点一次性清零重来;滑动窗口 = 随着时间滑走一点点还给你;漏桶 = 恒速往外放水;令牌桶 = 恒速往里发牌,且允许你攒着一次性花掉。

一个提前建立的直觉

评价一个限流方案只看三件事:准不准(会不会超卖容量)、快不快(每个请求多付多少延迟/网络往返)、扛不扛得住异常(Redis 挂了、实例扩缩容了怎么办)。后面所有架构取舍都是这三者的权衡。

阅读提示:术语不用背,忘了回来查这一页就行。真正要记住的只有一句:限流 = 额度计数器 + 不同的补充规则;以及"突发"这个词——它是区分四种算法的主线。
前置页:术语按“量纲 → 口径 → 机制 → 效果”组织。最小心智模型把四种算法统一为“额度计数器的不同补充规则”,为第 5-7 页的算法展开铺路;评价三问(准/快/扛异常)为第 8-9 页的架构取舍提供统一标尺。

Window Algorithms

固定窗口 vs 滑动窗口:临界突刺问题

按上一页的心智模型:这两种算法的差别,就是"额度什么时候还给你"——一次性清零,还是随时间滑走。

固定窗口与滑动窗口限流对比 上图固定窗口:以分钟为界计数,限流 100 次每分钟;窗口边界两侧各放 100 个请求时,两分钟内实际通过 200 次,瞬时速率翻倍,即临界突刺。下图滑动窗口:以最近 60 秒为统计区间随时间平移,任意时刻窗口内请求数不超过限额,消除边界突刺。 A · 固定窗口 FIXED WINDOW(计数器重置) 窗口 1(10:00-10:01) 计数 < 100 → 放行 窗口末涌入 100 个 窗口 2(10:01-10:02) 计数清零,又放 100 个 窗口初涌入 100 个 临界突刺:边界两侧各 100 个 → 2 分钟放了 200 个,瞬时 200 QPM = 限额的 2 倍 窗口越长越危险;秒级窗口可缓解但仍有 2 倍边界问题 B · 滑动窗口 SLIDING WINDOW(区间随时间平移) 两种实现 循环日志/环形桶(Sentinel) 时间片加权(滑动窗口日志) 最近 60s 窗口(任意时刻平移) 窗口内总数 ≤ 100 才放行 代价 内存:记录更细粒度样本 精度换内存,桶粒度权衡 t
窗口算法对比:固定窗口按周期计数重置,实现最简(Redis INCR+EXPIRE),但边界两侧各放满限额时瞬时速率翻倍——临界突刺。滑动窗口让统计区间随时间平移(环形桶实现,Sentinel 的方式),任意时刻窗口内不超限,代价是内存与精度权衡。答"为什么不用固定窗口"就答临界突刺。

Leaky Bucket

漏桶:流量整形(traffic shaping)

机制

  • 请求先进桶(队列),桶以恒定速率漏出处理;桶满则拒绝新请求
  • 效果:输出速率恒定——不管进来的是洪峰还是脉冲,出去永远是平稳流
  • 两个参数:桶容量(允许的排队上限)、漏出速率(处理能力)
  • 适用:保护"只能承受稳定速率"的下游——比如对第三方支付接口的调用(对方限速且计费敏感)

优缺点(面试直接背)

优点缺点
输出绝对平滑,强整流无法应对突发:即使系统空闲,突发的合法请求也只能按恒速排队
保护下游不给压力排队引入延迟;桶大小与延迟直接挂钩(带宽-延迟积)
实现简单(FIFO+定时器)分布式实现复杂(全局队列难做)

两种"漏桶"语义要分清(易混考点)

队列版:请求排队、恒速处理(本页所述,true shaping);② 计数版:有的实现把漏桶当"恒速放行计数器"(如每 1/r 秒放一个),效果等同。面试先声明自己说的是哪种,避免和令牌桶对比时张冠李戴。

漏桶在 Go 生态的落点

对下游限速的典型实现:golang.org/x/time/rate 是令牌桶(不是漏桶!);真正的漏桶多见于消息发送节流(如 Kafka producer 按字节速率节流)与出网关适配器。别把 x/time/rate 说成漏桶——这是常见翻车点。

漏桶核心:请求排队恒速漏出,输出绝对平滑,但拒绝突发(空闲时也不能加速)。两个易混点必须主动讲:漏桶两种语义(队列版/计数版);golang.org/x/time/rate 是令牌桶不是漏桶。适用场景:保护限速的第三方接口、出流量整形。

Token Bucket

令牌桶:允许突发的限流(工业界默认)

机制

  • 以速率 r 恒定投放令牌入桶(桶容量 b,令牌可积攒);请求取到令牌才放行,取不到拒绝
  • 空闲期令牌积攒 → 突发流量可一次性消耗积攒的令牌(最大突发 = b)→ 之后回落到速率 r
  • 两个参数:平均速率 r(长期吞吐)、桶容量 b(允许的最大突发)
  • 语义:平均限速 + 突发容忍——比漏桶更符合真实服务的"能扛短突发"能力

与漏桶对比(必考表)

维度漏桶令牌桶
输出速率严格恒定平均 r,允许突发至 b
突发流量排队(延迟)直接放行(消耗存量令牌)
控制对象流出速率(整形)流入速率(管制)
实现方向队列+定时消费定时/惰性补令牌
典型应用下游保护、QoS接口限流默认选型

Go 标准:golang.org/x/time/rate

limiter := rate.NewLimiter(
    rate.Limit(100), // r: 每秒100令牌
    200)             // b: 桶容量200
if !limiter.Allow() {   // 惰性补令牌
    // 429 Too Many Requests
}
limiter.Wait(ctx)  // 阻塞等令牌(可超时)

惰性计算:不靠定时器,取令牌时按时间差补发——零定时器开销,单机限流首选。

选型口诀与变体

保护自己接口 → 令牌桶(默认);保护脆弱下游/平滑流量 → 漏桶;要"窗口统计口径"(如报表/风控)→ 滑动窗口。变体:多桶组合(用户级+接口级+全局级串联,过三道闸)、预热令牌桶(Guava RateLimiter warmup:冷启动期速率低于 r,令牌产量爬坡——配合缓存预热场景)。

令牌桶三要点:机制(恒速投令牌、桶容量即突发预算)、与漏桶对比表(恒定 vs 允许突发、整形 vs 管制)、Go 实现 x/time/rate(NewLimiter(r, b)、惰性补令牌零定时器)。变体加分:多桶串联、预热爬坡(Guava warmup)。选型口诀:护自己令牌桶、护下游漏桶、要统计口径滑动窗口。

Distributed via Redis

分布式限流:Redis + Lua 的原子性

为什么必须 Lua(原子性问题)

// 错误做法:两条独立命令
n := GET key
if n < limit { SET key n+1 } // 竞态!
// 两个实例同时 GET 到 n=99
// 双双放行 → 超限

Redis 单线程执行脚本:Lua 里的"读-判-写"是一个原子操作,天然无竞态。配合 EVALSHA 减少传输、TTL 做窗口回收。

固定窗口 Lua(最简可背版)

local n = INCRBY key 1
if n == 1 then
  EXPIRE key window_sec
end
if n > limit then
  return 0          -- 拒绝
end
return 1            -- 放行

令牌桶 Lua 略复杂:存 {tokens, last_ts},每次按 (now-last_ts)*r 补令牌(封顶 b)再扣减——同样是"读-算-写"原子化。

滑动窗口 Lua 思路

ZSET 存请求时间戳:ZADD 当前请求 → ZREMRANGEBYSCORE 清窗口外 → ZCARD 计数判断。精确但内存高——只适合低频限流(如风控)

性能账

每次请求一次 Redis 往返(~1ms 内网):QPS 10w 时 Redis 单点 10w ops 压力大——引出下一页的"单机分摊"方案

可用性陷阱

Redis 挂了限流怎么办?fail-open(放行,靠服务自身容量扛)或 fail-close(拒绝,宁停不超)。一般选 fail-open + 本地限流兜底——限流组件不能成为新的单点

Redis+Lua 的考点三层:原子性(GET-SET 两命令有竞态,Lua 单线程原子)、脚本思路(固定窗口 INCRBY+EXPIRE 可背;令牌桶存 tokens/last_ts 按时间差补发;ZSET 滑动窗口精确但贵)、工程账(每请求一次往返的 QPS 上限、Redis 故障时 fail-open + 本地兜底)。fail-open/close 的选择是高级题。

Cluster Quota

集群限流的三种架构:精确、分摊、预测

1 中心化(精确)

所有实例实时问 Redis/专用限流服务(如 Sentinel Token Server 模式)。精确但:每请求一次网络往返、中心组件成为吞吐瓶颈与可用性依赖。

适合:低频高价值(支付、开放平台配额)。

2 配额分摊(准确实用)

总配额 10000 QPS ÷ N 实例 = 每实例 10000/N 本地限流;实例上下线时配额管理器重分配(心跳感知)。

流量在实例间不均时会有局部误杀——配合负载均衡尽量让流量均匀,容忍 ±10% 误差换零网络开销。工业界主流。

3 本地+全局分层

本地令牌桶做第一道(零成本、扛毫秒级洪峰)+ 低频全局校准(每秒同步一次全局计数,动态微调本地阈值)。

两层宽松叠加 ≈ 全局精确;延迟敏感场景的折中最优解。

为什么"每实例限 N/台"是错的(直觉陷阱)

静态平分不感知实例数变化:扩容 10→20 台后每台还限老值 → 总限流翻倍;缩容后 → 总限流减半误杀。必须有实例发现 + 动态重分配(对齐注册中心 deck 的实例列表)。同理流量倾斜时平分也会局部误杀——所以分摊方案要配合 LB 的均匀性。

一致性换性能的决策表

场景方案
计费/配额(必须准)中心化 + 异步补偿对账
保护容量(±10% 可接受)配额分摊
毫秒级洪峰(网关入口)本地第一道 + 全局校准
集群限流三架构:中心化(精确,每请求一次往返,适合计费配额)、配额分摊(总配额除以实例数本地执行,动态重分配,工业主流)、本地+全局分层(毫秒洪峰走本地,秒级校准走全局)。两个反直觉点要讲:静态平分不感知扩缩容是错的;分摊方案依赖 LB 均匀。决策按"错杀代价"选一致性等级。

Placement & Adaptivity

限流放哪一层 + 自适应限流(BBR 思想)

三层纵深(由外到内)

  • 网关层:粗粒度防刷(IP/用户/API Key)、全站预算、WAF 联动——挡住"非正常流量"
  • 服务层:按接口/资源的容量保护(令牌桶)+ 热点参数限流(Sentinel 热点规则:按参数值限流,如单个商品 ID 秒杀)
  • 资源层:DB/缓存连接池上限、线程/goroutine 池——最后防线(舱壁隔离的延伸)
  • 层间关系:阈值逐层收窄(网关 ≥ 服务 ≥ 资源),外层松内层严,避免外层放过内层全拒

自适应限流:让系统自己找阈值

  • 问题:静态阈值拍不准——流量结构变了(大促/新功能)、机器缩扩容,昨天压测的数字今天就错
  • SRE 思想:maxQPS = maxPass × minRT × 窗口系数(用最近窗口的"实际最大通过数×最小 RT"推算当前容量),CPU 超阈值或 qps 超算出来的 maxQPS 就拒绝
  • BBR/TCP 思想:测量瓶颈带宽×传播延迟(BDP),超过即在途请求过多 → 主动丢弃,拥塞自愈
  • go-zero 内置 CPU 自适应:CPU>90% 触发拒绝比例爬坡——阈值跟着机器状态走而不是配置走

热点参数限流(大厂高频)

普通限流限"总 QPS",热点限流限"某个参数值":秒杀场景全局 10w QPS 没问题,但"iPhone 发布"这一个商品 ID 占 8w——按商品 ID 滑窗计数,单 ID 超阈值即拒绝/排队,把热点从"全局风险"降为"局部事件"。

削峰填谷 vs 直接拒绝

可排队场景(异步任务提交)用漏桶/队列削峰,把洪峰摊平后慢慢处理;交互式请求(下单)不做排队——排队延迟会让用户反复重试反而放大流量,直接快速失败 + 前端友好提示更优。

分层限流三层纵深(网关防刷、服务容量、资源池底线),阈值逐层收窄。自适应限流是进阶核心:静态阈值会过期,SRE 公式 maxQPS=maxPass×minRT 用实际通过量推容量,BBR 用 BDP 测拥塞,go-zero CPU 自适应。热点参数限流(按参数值限流,秒杀单商品热点)是大厂高频。最后区分削峰填谷(异步可排队)与直接拒绝(交互式)。

Rejection

被限流之后:拒绝策略与用户体验

四种处理动作

  • 直接拒绝:返回 429 Too Many Requests + Retry-After 头——标准语义,客户端据此退避
  • 排队等待:进队列按速率消费(漏桶语义)——只适合可异步场景,交互接口慎用
  • 降级返回:拒绝真实处理但返回兜底数据(缓存旧值/默认页)——配合容错 deck 的降级
  • 优先级调度:VIP 请求优先过闸、低价值请求先拒——有限容量下的收益最大化

协议与体验细节

  • 状态码语义:429(限流)vs 503(过载/不可用)——网关限流用 429,服务熔断快速失败多走 503/Unavailable
  • Retry-After 必须带:让客户端退避而不是立刻重试(否则限流本身引发重试风暴)
  • 限流响应要幂等友好:写操作被拒后客户端重试不应产生副作用(对齐幂等 deck)
  • 前端配合:超限展示友好文案 + 指数退避重试按钮,绝不自动疯狂重试

限流的观测闭环

核心指标:限流触发次数(按维度细分)、被拒请求的特征分布(哪个 IP/用户/接口)、限流后的服务 SLO。告警规则:限流率突增 = 攻击或热点事件,自动通知——限流不是配完就完,是持续运营的开关。

压测验证限流正确性

发压超过阈值:验证① 通过量稳定在阈值附近(不超卖容量);② 被拒请求快速返回 429(不堆积占资源);③ 阈值下调时在线生效;④ Redis 故障时 fail-open 行为符合预期。限流不验证 = 上线后第一次大促必翻车。

拒绝策略四动作:直接拒绝(429+Retry-After)、排队(仅异步)、降级返回、优先级调度。协议细节:429 与 503 的语义分工、Retry-After 必带(防限流引发重试风暴)、限流响应的幂等友好。收尾两块:限流率是运营指标要告警;限流必须压测验证四条。

Cheat Sheet

一页带走:算法 · 架构 · 治理

① 四种算法:差别只在"额度怎么补"

固定窗口到点清零重来;最简(Redis INCR+EXPIRE),但边界两侧可放 2 倍 → 临界突刺
滑动窗口区间随时间平移,任意时刻不超限;消除突刺,代价是记录样本的内存
漏桶排队 + 恒速漏出;输出绝对平滑,但拒绝突发(空闲也不能加速)→ 护下游
令牌桶 ★恒速发令牌、可积攒;平均限速 + 突发预算 b → 接口限流默认选型(x/time/rate)
一句话区别漏桶 整形(排队,加延迟);令牌桶 管制(超限直接丢)

② 分布式:按"错杀代价"选架构

中心化每次问 Redis → 精确;代价是每请求一次往返 + 中心成为单点。适合计费/配额
配额分摊 ★总配额 ÷ 实例数,本地执行;需感知实例数变化动态重分配。±10% 误差换零开销,工业主流
本地 + 全局本地令牌桶扛毫秒洪峰 + 秒级全局校准;延迟敏感场景的折中最优
Redis 挂了默认 fail-open(放行)+ 本地令牌桶兜底;限流组件不能成为新单点

③ 阈值从哪来(答"设多少")

压测找拐点延迟开始上翘 / 错误率开始出现的那个 QPS
乘安全系数阈值 = 拐点 × 70~80%(留给 GC、抖动、依赖波动)
分层梯度网关 ≥ 服务 × 副本数 ≥ 资源池;外层松内层严,别让请求"过了网关又全被拒"
阈值会过期扩缩容 / 流量结构变化都会让静态阈值失准 → 自适应(CPU、maxPass×minRT、BBR/BDP)

④ 被拒之后:四选一

直接拒绝429 + Retry-After(必带,否则引发重试风暴);交互式请求的首选
排队等待只适合异步任务;交互接口排队会让用户反复重试、反而放大流量
降级返回返回缓存旧值 / 默认页——有损但可用
优先级调度有限容量下保核心(登录用户 > 游客、读 > 低价值写)

⑤ 与其它防线的分工

限流是事前(流量超容量就拒,服务端视角)· 熔断是事中(依赖故障就停调用,调用方视角)· 降级是善后(被限/被熔后给用户什么)。
运营三指标:限流率、熔断打开数、降级率——限流配完还要压测验证四条(通过量贴阈值、被拒快速返回、阈值在线生效、fail-open 行为符合预期)。
速查页:五块按答题顺序排(先算法、再分布式架构、再阈值、再拒绝策略、最后与熔断降级的分工)。算法表回指第 5-7 页,架构表回指第 8-9 页,阈值与拒绝回指第 10-11 页。

Interview QA · 1/2

高频 QA(一):算法与实现

先盖住答案自己答一遍,再展开对照——想不起来比看得顺眼记得牢;答不出的直接翻回上一页速查表。

Q1 四种限流算法怎么选?

突发容忍统计口径

接口限流默认令牌桶(平均速率+突发预算,x/time/rate 即此);保护脆弱下游或需要严格平滑 → 漏桶;需要"窗口统计"语义(风控/报表口径)→ 滑动窗口;临时兜底/配置最简 → 固定窗口(知道边界突刺即可)。先说场景再说算法,别背顺序。

Q2 固定窗口的临界突刺,除了换滑动窗口还有救吗?

细粒度窗口双窗口

缓解手段:① 缩短窗口粒度(分钟→秒级,突刺比例不变但绝对量变小);② 双窗口/加权:当前窗口计数 + 上一窗口剩余比例加权预估(近似滑动,O(1));③ 突刺无害化:把阈值按"窗口边界最坏情况"设定(直接按 2 倍瞬时设计容量)。但根治还是滑动窗口/令牌桶。

Q3 令牌桶和漏桶的本质区别一句话?

管制 vs 整形

令牌桶限制的是"平均流入速率、允许突发"(traffic policing,直接丢弃超出的);漏桶强制"恒定流出速率"(traffic shaping,排队平滑)。形象记法:令牌桶是"攒了票就能进",漏桶是"不管多少人排队,检票口恒速"。

Q4 分布式限流为什么用 Lua 而不是事务/分布式锁?

原子性开销对比

需要"读-判-写"原子。Redis 事务(MULTI/EXEC)不能先读再条件写(EXEC 前拿不到结果做判断);分布式锁是"串行化执行",每请求加解锁两次往返,QPS 掉一个量级。Lua 在服务端单线程原子执行,一次往返搞定——正确且最快。

Q5 Redis 挂了限流怎么办?

fail-open本地兜底

一般 fail-open:放行请求,靠服务自身容量与降级预案扛(限流组件不能成为新单点);高价值场景(计费配额)可 fail-close。工程标配:Redis 不可用时自动降级到本地令牌桶(阈值按单机分摊),恢复后切回——两种模式都要在压测里演练过。

Q6 集群总限流 1w QPS、50 台实例,怎么做?

动态分摊分层校准

标准答案:配额分摊——每实例本地限 200,配额管理器(或利用注册中心实例列表)动态感知实例数变化重分配;流量不均导致 ±10% 误差可接受。更高精度:本地第一道 + 每秒向 Redis 汇报/校准一次全局计数微调本地阈值。全中心化(每请求问 Redis)只在计费级场景用。

六题:算法选型(按场景)、临界突刺的三种缓解(细粒度/双窗口/按最坏设计)、令牌桶漏桶一句话(policing vs shaping)、Lua vs 事务 vs 锁(事务不能条件写、锁两次往返)、Redis 故障 fail-open+本地兜底、集群分摊动态重分配。

Interview QA · 2/2

高频 QA(二):场景与治理

同样建议先自答。这一页的题都要"结论 + 一句代价/边界"才完整——只答结论容易被追问。

Q7 秒杀系统的限流怎么做?

漏斗模型热点参数

层层漏斗:前端答题/按钮置灰防重复点击 → 网关按用户/IP 限流防刷 → 服务层按商品 ID 热点参数限流 → 库存扣减用 Redis 原子预扣(超卖保护)→ MQ 削峰异步落库。核心思想:流量在每一层都被"合法地"削减,到 DB 只剩库存量级。限流阈值 = 库存量×安全系数而不是机器容量。

Q8 限流阈值怎么科学设定?

压测拐点动态调优

三步:① 全链路压测找拐点(延迟开始上翘、错误率开始出现的 QPS);② 阈值 = 拐点 × 70-80% 安全系数(留缓冲给 GC/抖动/依赖波动);③ 上线后按 SLO 动态调:P99 达标且限流率高 → 阈值偏保守可上调;P99 超标 → 下调或扩容。自适应限流(CPU/负载维度)解决"阈值过期"问题。

Q9 限流、熔断、降级三者关系?

三道防线

限流是事前:流量超过容量就拒,防止进入过载状态(服务端视角);熔断是事中:依赖故障时停止调用,防止被拖垮(调用方视角);降级是善后:被限/被熔之后给用户什么(有损响应)。三者的观测指标:限流率、熔断打开数、降级率——大屏三板斧。

Q10 网关限流和服务限流会不会双重拒绝?

阈值梯度

会,且这是设计目标(纵深防御),但要控制梯度:网关阈值 ≥ 服务阈值 × 服务副本数,且网关略宽松——让"正常的洪峰"被服务层精确保护,"恶意流量"被网关层提前挡掉,避免大部分请求"过了网关又全被服务拒"(白付了网关成本还体验差)。

Q11 限流如何做到对用户无感?

优先级排队页

不可能完全无感(被拒就是被拒),目标是"体验可控":① 优先级保核心(登录用户 > 游客、读 > 写低价值);② 可排队场景给排队页/进度反馈(抢票模型),把"被拒"变成"稍等";③ 前端退避重试 + 兜底数据展示;④ 预估等待时间真实可信(别让用户白等)。

Q12 如何排查"线上突然大量限流"?

先分类再看维度

先分类:正常业务增长(流量涨了)→ 扩容或调阈值;流量结构异常(单用户/单 IP 占比高)→ 防刷策略;依赖变慢导致上游重试放大 → 修复依赖 + 查重试预算;攻击 → WAF/封禁。工具链:按限流维度的 TopN 分布(哪个用户/接口触发最多)→ 追对应调用链 trace。限流指标要能下钻到维度,否则没法排障。

六题场景:秒杀漏斗模型(每层合法削减流量,阈值按库存不按机器)、阈值科学设定(压测拐点×安全系数+动态调优)、三道防线关系(限流事前/熔断事中/降级善后)、双重拒绝的梯度设计(网关≥服务×副本)、无感体验(优先级/排队页/退避)、大量限流排查四分类。Q7 必练白板画图。

Related & References

相关知识点与参考

本领域相关 deck

跨领域相关 deck

参考链接(一手来源)

收尾链接:领域内容错(三道防线)、网关(挂载点)、负载均衡与发现(分摊基础)、配置中心(阈值下发)、可观测(限流率告警);跨领域 Redis 数据结构与单线程模型(Lua 原子性根基)、Kafka(削峰)、OS 调度(自适应负载信号)。参考:x/time/rate、Sentinel、Redis EVAL、SRE Workbook、BBR 论文。