Chapter 09

限流、熔断、降级

系统稳定性的三道防线。在洪峰流量和级联故障面前,
用精确的算法和清晰的策略保护核心业务。

为什么需要三道防线

没有防护的系统: 正常流量 1,000 QPS ──▶ 系统处理正常 突发流量 10,000 QPS ──▶ DB 连接池耗尽 ──▶ 请求积压 ──▶ 内存溢出 ──▶ 全服务崩溃 ──▶ 恢复缓慢 ──▶ 雪崩效应 三道防线(由外到内): ① 限流(Rate Limiting):在入口拦截超量请求 「每秒最多接受1000个请求,超出直接拒绝」 ② 熔断(Circuit Breaking):检测下游故障,快速失败 「下游失败率 > 50%,立即断路,不再转发请求」 ③ 降级(Degradation):核心功能正常,非核心功能关闭 「支付正常 → 推荐系统可以关闭,评论可以只读」

限流算法详解

算法一:固定窗口(Fixed Window)

固定窗口计数器: 时间窗口:每1秒重置一次计数器 限制:每秒最多 100 个请求 12:00:00.000 ~ 12:00:01.000 窗口: ├── 请求1 计数=1 ✓ ├── 请求2 计数=2 ✓ ├── ... ├── 请求100 计数=100 ✓ └── 请求101 计数=101 ✗ 拒绝! 问题:窗口边界攻击! ┌────────────────────┬───────────────────┐ │ 00.9s 内 100次 │ 01.0s 后 100次 │ └────────────────────┴───────────────────┘ ↑ 这0.2s内实际通过了200次请求!

算法二:滑动窗口(Sliding Window)

滑动窗口: 当前时间:12:00:01.500 窗口大小:1秒 统计范围:12:00:00.500 ~ 12:00:01.500 ────────────────────────────────────────────── 12:00:00.0 请求×20 12:00:00.4 请求×30 ← 这50个在窗口外 12:00:00.6 请求×30 ← 这30个在窗口内 12:00:01.0 请求×40 ← 在窗口内 12:00:01.4 请求×10 ← 在窗口内 ────────────────────────────────────────────── 当前计数 = 30 + 40 + 10 = 80 (< 100,允许) 优点:平滑,无边界突刺问题 缺点:需要记录每条请求的时间戳(内存开销大) 实现:Redis ZSet,Key=IP,Score=时间戳,Value=请求ID ZADD ip:1.2.3.4 1700000000.123 req-uuid ZREMRANGEBYSCORE ip:1.2.3.4 0 (now-1s) // 移除窗口外的 count = ZCARD ip:1.2.3.4 // 当前窗口计数

算法三:漏桶(Leaky Bucket)

漏桶算法: 请求涌入(速率不固定) │││││ ▼▼▼▼▼ ┌──────────┐ ← 桶满则溢出(丢弃请求) │ │ │ 桶 │ 容量:100 │ │ └────┬─────┘ │ 匀速流出(固定速率) ▼ 下游服务(10 req/s) 优点:完全平滑输出,下游永远以固定速率接收请求 缺点:突发流量被强制排队或丢弃,无法利用系统瞬时余量 适用:需要严格保护下游的场景(如第三方 API 调用)

算法四:令牌桶(Token Bucket)— 推荐

令牌桶算法: 令牌生成器以固定速率往桶里放令牌: ┌──────────────────────────────────────────────┐ │ Token Generator: 100 tokens/second │ │ │ │ ┌──────────────────────┐ │ │ ───▶ │ Token Bucket │ │ │ 放入 │ ● ● ● ● ● ● ● ● ● ● │ 容量 100 │ │ │ ● ● ● ● ● ● ● ● ● ● │ │ │ └──────────┬───────────┘ │ │ │ │ │ 请求到来:拿走一个令牌 → 处理请求 │ │ 无令牌:拒绝或等待 │ └──────────────────────────────────────────────┘ vs 漏桶的区别: 漏桶:输出速率固定,无法利用突发余量 令牌桶:平时积累令牌,突发时可以消耗积累的令牌 允许短暂的流量突刺(Burst),更实用! 例子:平时 10 req/s,桶满100个令牌 突然来了200个请求: 前100个:消耗存量令牌,通过! 后100个:新令牌还没生成,拒绝 实现(Redis + Lua 原子操作): tokens = min(capacity, last_tokens + rate * elapsed) if tokens >= 1: tokens -= 1 allow = True else: allow = False
算法允许突刺平滑度实现复杂度推荐场景
固定窗口边界处可简单场景
滑动窗口精确限流
漏桶极好保护下游
令牌桶通用推荐

熔断策略

熔断器在第8章已详细介绍三态状态机。这里重点关注熔断触发条件的配置

Resilience4j 熔断配置示例: CircuitBreakerConfig { slidingWindowSize: 10 // 统计最近10次请求 failureRateThreshold: 50 // 失败率 > 50% 触发熔断 slowCallRateThreshold: 80 // 慢调用率 > 80% 也触发 slowCallDurationThreshold: 2s // 2秒以上算慢调用 waitDurationInOpenState: 30s // OPEN 状态等待30秒后进入 HALF-OPEN permittedCallsInHalfOpen: 3 // HALF-OPEN 时允许3个探测请求 minimumNumberOfCalls: 5 // 至少5次调用才开始统计 } 熔断 vs 限流 的区别: 限流:我限制自己能发多少请求(入口控制) 熔断:我检测到你(下游)出了问题,我不再调用你(出口保护)

降级策略

降级是在系统资源不足或部分组件故障时,主动关闭非核心功能,保证核心链路畅通。

降级优先级划分(电商系统为例): P0 核心链路(绝不降级): 用户登录 → 商品展示 → 加入购物车 → 下单 → 支付 P1 重要功能(尽量保留): 订单查询、物流查询、退款申请 P2 辅助功能(可以降级): 商品评论(降级为:暂时无法查看评论) 推荐算法(降级为:固定热销商品列表) 实时库存显示(降级为:显示「有货」) P3 增值功能(直接关闭): 猜你喜欢 最近浏览 用户行为统计上报 降级的四种形式: ① 返回默认值:推荐列表返回热销 Top10 ② 返回缓存数据:即使缓存过期,也返回旧数据(stale cache) ③ 简化处理:评论列表只返回前5条 ④ 完全关闭:直接返回「功能暂时不可用」

超时设置原则

超时设置的艺术: 场景:API Gateway → Service A → Service B → DB 如果不设超时: DB 慢查询 60s → Service B 等待 60s → Service A 等待 60s(连接池耗尽) → API Gateway 等待 60s(线程耗尽) → 前端白屏 60s → 用户不断重试 → 雪崩! 正确的超时设置(从内到外递减): DB 查询超时: 5s Service B 超时: 6s(= DB超时 + 处理时间缓冲) Service A 超时: 8s(= B超时 + 处理时间缓冲) API Gateway 超时:10s(= A超时 + 处理时间缓冲) 前端超时: 12s 原则: 外层超时 > 内层超时(给内层时间先超时并返回) 超时 + 重试要配合熔断(否则重试会放大压力) 不同操作设置不同超时:读操作100ms,写操作500ms

SLA 定义与 SLO/SLI

SLA
服务级别协议
Service Level Agreement。与用户/客户签订的合同承诺,违反了要赔钱。如:「API 可用性 >= 99.9%,否则赔偿服务费」。
SLO
服务级别目标
Service Level Objective。团队内部设定的技术目标,比 SLA 更严格。如内部 SLO 是 99.95%,对外 SLA 承诺 99.9%(留缓冲区)。
SLI
服务级别指标
Service Level Indicator。衡量 SLO 的具体度量指标。如:可用性 = 成功请求数 / 总请求数;延迟 = P99 响应时间。
Error Budget
错误预算
SLO 允许的失败空间。99.9% SLO → 每月 43.8 分钟可用于故障/发布。预算耗尽 → 冻结发布,专注稳定性。
▶ 面试要点