限流、熔断、降级:系统的自我保护
Lesson 0002 教你算容量,但容量算得再准也挡不住尖峰与故障。限流防被打死、熔断防被拖死、降级防被吓死——三件套是面试「你的系统挂了怎么办」的标准答案库,也是工作中值班不爆炸的底气(Stripe、NGINX 的工程实践是主要参考[1][3])。
§1 · 限流Gatekeeper 的四种算法
限流(rate limiting)回答「超过容量的请求怎么办」:快速拒绝(HTTP 429 + Retry-After 头),好过全系统排队慢慢死。四种经典算法[1]:
| 算法 | 机制 | 特点 |
|---|---|---|
| 固定窗口 | 每分钟一个计数器,超过就拒 | 实现最简单;窗口边界突刺:两分钟交界处可达 2 倍限值 |
| 滑动窗口日志 | 记录每个请求的时间戳,窗口内超数就拒 | 最精确;内存开销大(O(请求数)) |
| 滑动窗口计数 | 相邻两窗口按时间加权插值 | 精度与内存的折中,常用 |
| 令牌桶 ✅ | 恒速往桶里放令牌,请求取令牌,桶空则拒 | 允许受控突发(桶是突发额度),参数直观(速率+桶深),工业首选 |
漏桶(leaky bucket)与令牌桶常被混谈:漏桶是恒定速率放行(把突刺削平),令牌桶是平均速率 + 允许突发。选哪个取决于你想「削平」还是「允许瞬时」。详细对比与伪代码见 限流算法速查表。
多实例部署时「每实例 100 QPS」≠「全局限 1000 QPS」(流量不均)。全局限流要把计数放到 Redis(原子 INCR + 过期,或 Lua 脚本)——而这一步又引入 Redis 依赖与竞态,工程上常按「实例级粗限 + 全局限精调」双层做。面试里说出「单机限流不等于全局限流」这句话就赢过大多数人。
§2 · 熔断止损的艺术
熔断器(circuit breaker)借自电路保险丝,Fowler 的定义是标准参考[2]。它防的是级联失败:下游 B 变慢 → 你的线程全在等 B → 耗尽 → 你也挂 → 上游跟着挂,全链路雪崩。
| 状态 | 行为 | 切换条件 |
|---|---|---|
| Closed | 正常放行 | 失败率/慢调用率超阈值(如 50%)→ 转 Open |
| Open | 直接快速失败,不再碰下游 | 冷却时间(如 30s)到 → 转 Half-Open |
| Half-Open | 放少量试探请求 | 试探成功 → Closed 恢复;失败 → 回 Open 再冷却 |
两个配套:超时——所有跨网络调用必须有超时,没超时的系统熔断都救不了;指数退避重试(exponential backoff + jitter)——重试要间隔翻倍并加随机抖动,否则「重试风暴」会把故障放大 N 倍(与 L7 的 DLQ 相接:重试到上限进死信)。
§3 · 降级有舍才有得
降级(graceful degradation)回答「资源不够时,保什么、舍什么」:
- 功能降级:大促时关闭「猜你喜欢」「评论区」,保交易主链路——非核心功能配开关(feature flag);
- 数据降级:推荐服务挂了 → 返回默认热门榜单;个性化挂了 → 返回静态兜底页;
- 读降级:DB 压力大 → 返回缓存中的稍旧数据(L9:本质是主动选 AP)。
三件套的关系一句话:限流是进门排队(拒绝外人),熔断是发现着火切断邻居,降级是着火时决定先搬什么走。
§4 · 随堂检测
§5 · 检索练习
凭记忆回答:① 令牌桶的两个参数各控制什么?它和漏桶的本质区别?② 熔断器三状态与切换条件?③ 为什么重试必须加指数退避和抖动?
核对参考答案
① 速率(稳定流量)+ 桶深(突发额度);令牌桶允许突发、漏桶恒速放行削平突发。② Closed→(失败率超阈)→Open→(冷却到点)→Half-Open→(试探成功/失败)→Closed/Open。③ 故障期全量重试会形成重试风暴放大故障;退避拉开间隔、抖动避免同时起跳。完整对照见 限流算法速查表。
§6 · 本周行动
挑你系统的一个外部依赖(第三方 API、ES、下游服务),逐项回答:
① 调用有超时吗?设多少?依据是什么?
② 有熔断吗?失败阈值和冷却时间?
③ 重试带指数退避和抖动吗?上限几次?之后去哪(DLQ?)?
④ 它挂了,你的页面/接口给用户什么?(降级预案存在吗?)
§7 · 延伸资源
- 首选精读:Stripe: Scaling your API with rate limiters——工业界令牌桶实践的天花板[1]
- Fowler: Circuit Breaker——熔断模式的经典定义[2]
- 参考文档:限流算法速查表(四算法对比 + 分布式实现要点)
- 下一课 → Lesson 0011 Feed 流:推 vs 拉