路线图

12 分布式可靠性模式

星辉 2026-07-02 阅读 4 min 798 字 路线图
12 分布式可靠性模式 封面

概述#

分布式系统不能靠"希望不出问题"来保证可靠性。本章展开五种核心可靠性模式——冗余降级、熔断、限流、重试退避、故障隔离——它们共同构成服务韧性的基础层,是 SRE 在实际系统设计中最大的技术价值输出。

分布式系统的故障是必然的——网络会抖、节点会挂、依赖会慢。可靠性设计不是"防止故障",而是"让故障的影响可控"。这五种模式就是控制故障影响的工程手段。

一、冗余与降级:可靠性设计的起点#

在进入具体模式之前,先明确两个基础概念:

概念含义案例
冗余(Redundancy)多副本/多活部署,单个节点故障不影响整体K8s Deployment replicas≥2、多 AZ 部署
降级(Degradation)部分功能不可用时,核心流程仍可走通推荐系统挂了,首页仍能展示

降级的核心是优雅地部分失败(Fail Partial),而不是全部崩溃。比如支付不可用时购物车仍然可用,只是结账时告之稍后重试。

冗余的层次#

层次做法防护的故障
Pod 级replicas ≥ 2单 Pod crash
节点级PodAntiAffinity 跨节点单节点故障
AZ 级跨可用区部署单 AZ 故障
Region 级多 Region 异步复制单 Region 灾难

每多一层冗余,成本翻倍但可用性提升一个数量级。要根据服务的 SLO 决定冗余层次——内部工具 Pod 级就够,核心交易要 AZ 级,全球服务要 Region 级。

降级的设计原则#

降级不是"挂了就返回错误",是"挂了就返回次优响应":

  • 推荐服务挂 → 返回热门商品列表(缓存)
  • 搜索服务慢 → 返回简化结果(少聚合)
  • 用户画像挂 → 返回默认画像
  • 评论服务挂 → 商品页不显示评论但能下单

降级需要预先设计——不是事故时临时决定"要不要降级",而是代码里就写好了 fallback 逻辑,依赖不可用时自动触发。

二、熔断(Circuit Breaker)#

问题#

A 服务调用 B 服务,B 服务响应变慢。A 的线程池被一堆等待 B 响应的请求占满,然后 A 也无法响应上游。雪崩由此开始——一个下游的慢最终拖垮整个调用链上游。

text
B 慢 → A 线程池被 B 的请求占满 → A 无法响应其他请求
  → A 的上游也卡住 → 整个调用链雪崩

这是分布式系统最常见的故障传播模式——不是"挂了传播",而是"慢了传播"。慢比挂更危险,因为挂会快速失败,慢会持续占用资源。

解决方案:三状态熔断器#

text
          ┌──────────┐
          │  CLOSED  │  ← 正常状态,请求正常通过
          └────┬─────┘
    失败次数达阈值│
          ┌──────────┐
          │   OPEN   │  ← 熔断打开,请求直接拒绝(快速失败)
          └────┬─────┘
      冷却时间到  │
          ┌──────────┐
          │ HALF-OPEN│  ← 试探性放行少量请求
          └────┬─────┘
     成功 → CLOSED │ 失败 → OPEN

三状态详解#

状态行为目的
CLOSED(闭合)请求正常通过,统计失败次数正常工作
OPEN(断开)请求立即返回错误,不实际调用下游保护下游不被持续打挂,让线程池不阻塞
HALF-OPEN(半开)放行少量探测请求,成功则恢复 CLOSED探测下游是否已恢复

关键参数#

参数建议值含义
失败阈值5-10 次(1 分钟窗口内)触发 OPEN
熔断时长30s-60sOPEN 持续多久后进 HALF-OPEN
HALF-OPEN 探测数1-3 个请求试探多少请求
失败定义超时 + 5xx + 连接错误什么算"失败"
慢调用阈值延迟 > 2s 算失败防止"慢但不报错"拖垮系统

实际案例#

摘自 A 旗舰发布场景:某次发布中,payment-api 因数据库慢查询导致调用下游 signal-api 超时。未配熔断时,payment-api 的 gRPC 连接池被所有等待 signal-api 超时的请求占满,导致 payment-api 无法响应任何请求。加熔断后,signal-api 异常时 payment-api 直接快速失败返回 fallback 响应,主流程不受影响。

熔断器实现注意事项#

  • 区分不同错误类型(超时 vs 5xx 可能需要不同的熔断阈值)
  • 熔断器本身需要监控(OPEN 次数、HALF-OPEN 成功率)
  • 在网关层做粗粒度熔断,在服务调用层做细粒度熔断
  • 熔断 OPEN 时要有 fallback 逻辑(返回缓存、默认值、降级响应)
  • 熔断器状态变化要发告警——OPEN 是异常状态,需要关注

常见实现#

  • Hystrix(Java,停止维护,但概念经典)
  • Resilience4j(Java,Hystrix 的现代替代)
  • Sentinel(阿里开源,功能丰富)
  • Istio OutlierDetection(服务网格层,语言无关)
  • Envoy(代理层熔断)

服务网格层熔断(Istio/Envoy)的好处是语言无关、配置统一;应用层熔断(Resilience4j/Sentinel)的好处是细粒度、可结合业务逻辑。两者通常组合使用。

三、限流(Rate Limiting)#

令牌桶(Token Bucket)#

最常用的限流算法:

text
令牌生成速率 = R 个/秒
桶容量 = B 个令牌(允许的突发量)

请求到达 → 取令牌 → 有 → 放行
                  → 无 → 拒绝(或排队)
参数含义典型值
Rate(R)令牌生成速率如 1000 QPS
Burst(B)桶容量,允许的突发请求数如 R × 2

令牌桶的特点是允许突发——桶里有令牌时可以瞬间处理多个请求,适合"平时低流量、偶尔突发"的场景。

漏桶(Leaky Bucket)#

以恒定速率处理请求,超出排队的丢弃:

text
请求流入(任意速率)→ [漏桶队列] → 固定速率流出处理

漏桶的特点是强制平滑——不管请求多突发,处理速率恒定。适合"必须匀速处理"的场景,如消息队列消费。

两种算法对比#

令牌桶漏桶
突发处理允许突发拒绝突发
实现复杂度
适用场景API 网关、用户请求消息队列、后台任务
用户体验好(偶尔突发不被拒)一致(但突发会被拒)

限流层次#

层级粒度工具
入口网关按 IP / API KeyIstio RateLimit / NGINX / Envoy
服务间调用按 caller serviceSidecar 限流、Sentinel
业务层按用户 / 租户自定义中间件
数据库按连接数连接池限流

多层限流是必要的——入口限流防 DDoS,服务间限流防雪崩,业务层限流防单用户滥用,数据库限流防连接耗尽。

限流 vs 熔断 的区别#

熔断限流
触发条件下游错误率高/响应慢请求量超阈值
目的保护自己不被慢下游拖垮保护下游不被过量请求打挂
粒度按"下游健康"动态切换按 QPS/并发数硬限制
动态性自适应(根据错误率自动开/关)通常是静态配置
方向阻断"自己→下游"的调用阻断"上游→自己"的请求

两者互补:限流防过载,熔断防雪崩。一个完整的可靠性方案两者都需要。

限流的响应策略#

被限流时怎么响应?四种选择:

  • 拒绝:返回 429 Too Many Requests(最简单)
  • 排队:请求进入队列等待(适合可延迟任务)
  • 降级:返回缓存或简化结果(用户体验好)
  • ** Shed Load**:丢弃部分请求保核心(极端过载时)

选择取决于业务——用户请求适合拒绝或降级,后台任务适合排队。

四、重试退避(Retry with Backoff)#

为什么要重试#

分布式系统中,网络抖动、服务临时过载、Pod 重启导致的瞬时不可用是常态。适当的重试能把这些瞬时故障从"用户可见的错误"变成"透明恢复"。

统计显示,分布式系统中 90%+ 的故障是瞬时的(< 1 秒)。不重试 = 把 90% 的可恢复故障变成用户可见错误。

重试的三大风险#

  1. 重试风暴:下游已经够慢了,上游再重试 3 次 → 请求量变成 3 倍 → 雪崩加速
  2. 非幂等操作重试:扣款请求被重试 → 用户被扣两次
  3. 无限制重试:永远重试 → 线程池耗尽

正确的重试策略#

策略做法适用场景
指数退避(Exponential Backoff)重试间隔 100ms → 200ms → 400ms → 800ms通用
指数退避 + Jitter退避间隔加随机抖动,避免"惊群效应"大规模集群
最大重试次数3 次,超过则失败通用
总超时限制所有重试总时间不超过 X 秒用户体验敏感
仅重试可重试错误429/503/超时可重试,400/401 不重试按错误类型
circuit breaker 联动熔断 OPEN 时不重试防止重试打挂下游

Jitter(抖动)的必要性#

假设 100 个调用方同时因下游故障触发重试。如果都用固定间隔(1s),它们会在同一时刻同时重试 → 下游从"挂掉"变成"被 100 个请求瞬间打死"。加上随机 Jitter 后,重试分散在 1s-2s 的时间窗口内 → 下游有喘息空间。

text
无 Jitter:  T+1s → 100 个请求同时到达  ← 惊群
有 Jitter:  T+1s~2s → 100 个请求散布在 1s 窗口内

Jitter 的实现:

python
import random
import time

def retry_with_jitter(func, max_retries=3):
    for attempt in range(max_retries):
        try:
            return func()
        except RetryableError:
            if attempt == max_retries - 1:
                raise
            # 指数退避 + Jitter
            base = 0.1 * (2 ** attempt)  # 100ms, 200ms, 400ms
            jitter = random.uniform(0, base / 2)  # 0 ~ base/2 的随机
            time.sleep(base + jitter)

幂等性是重试的前提#

重试必须保证幂等——同样的请求执行多次和一次效果相同。

操作类型幂等性重试安全性
GET / PUT幂等安全重试
DELETE幂等(删除已删除的返回 404 但效果相同)安全重试
POST(创建)非幂等需要幂等键
支付/扣款非幂等必须幂等键

非幂等操作的重试必须用幂等键(idempotency key)——客户端生成唯一 ID,服务端去重。这是支付系统的标配。

五、故障隔离(Fault Isolation)#

舱壁模式(Bulkhead)#

从船舶设计借用的概念:船体被分隔成多个水密舱,一个舱进水不会让整艘船沉没。

在软件架构中:

隔离维度做法效果
线程池隔离不同下游使用独立线程池慢下游只占满自己的池,不影响其他下游
连接池隔离不同数据库使用独立连接池一个 DB 连接满不影响另一个 DB
部署隔离核心服务和非核心服务分离部署非核心 OOM 不影响核心
租户隔离大客户单独资源池一个大客户的突发流量不影响其他客户
资源隔离CPU/内存 limits一个 Pod 不吃光节点资源

线程池隔离的工程实现#

text
调用方服务
├── 线程池 A(10 线程)→ 下游 A(慢)
├── 线程池 B(10 线程)→ 下游 B(正常)
└── 线程池 C(5 线程) → 下游 C

→ 下游 A 变慢,占满 10 线程,但 B 和 C 不受影响

如果不做隔离:所有请求共享一个线程池,下游 A 慢 → 线程池被占满 → B 和 C 也无法调用。

K8s 层面的隔离#

  • PodDisruptionBudget(PDB):限制同时不可用的 Pod 数量
  • PodAntiAffinity:确保同服务 Pod 不打到同一个节点
  • Resource Requests/Limits:防止一个 Pod 吃光节点资源
  • NetworkPolicy:限制不必要的网络访问
  • PriorityClass:高优先级 Pod 优先调度
  • NodePool 专用:核心服务用专用节点池

租户隔离的层次#

SaaS 系统的租户隔离有多个层次:

层次隔离强度成本适用
共享应用 + 逻辑隔离小客户
独立 Pod + 共享 DB中客户
独立 namespace + 独立 DB大客户
独立集群最强最高企业客户

隔离层次选择取决于客户规模和付费水平——小客户共享资源摊低成本,大客户独立资源保证性能。

六、降级(Degradation)#

降级是故障发生时的"应急计划"——核心功能保留,非核心功能关闭。

降级的类型#

类型做法例子
功能降级关闭非核心功能关闭评论、推荐
数据降级返回旧数据或缓存DB 挂了返回缓存
体验降级降低精度但保可用搜索返回少量结果
流量降级拒绝部分请求拒绝新用户注册,保老用户

降级的触发#

降级可以是:

  • 手动触发:运维人员判断后开启(响应慢但可控)
  • 自动触发:基于指标自动开启(响应快但可能误判)
  • 混合模式:自动触发 + 人工确认(折中)

自动触发的指标:

  • 错误率 > 5% → 降级非核心功能
  • P99 延迟 > 2s → 简化响应
  • CPU > 90% → 拒绝低优先级请求
  • DB 连接池 > 90% → 返回缓存

降级的测试#

降级逻辑必须定期演练——不演练的降级 = 没有降级。常见问题:

  • 降级开关本身挂了
  • 降级后的 fallback 数据过期
  • 降级逻辑有 bug(平时不跑,关键时刻出错)
  • 降级后核心功能也不可用(依赖没断干净)

混沌工程(见 Ch15)是测试降级的最佳方式。

七、总结对比表#

模式解决的问题核心机制触发条件实现层
冗余单点故障多副本部署常驻架构层
降级部分故障整体可用Fallback 逻辑依赖不可用时应用层
熔断下游慢拖垮上游三状态自动切换错误率/延迟超阈值应用/网格层
限流过量请求打挂下游令牌桶/漏桶请求量超配额网关/应用层
重试退避瞬时故障导致用户可见错误指数退避+Jitter特定错误类型应用层
舱壁隔离故障扩散线程池/连接池/部署隔离常驻架构层

实战要点#

  1. 熔断+限流+重试必须组合使用,单用任何一个都有盲区。
  2. 重试只在幂等操作上做。扣款/下单等非幂等操作要么不重试,要么用幂等键保证安全。
  3. Jitter 是防重试风暴的关键。不加 Jitter 的退避基本等于定时炸弹。
  4. 舱壁是最容易被忽视的隔离手段——它不像熔断那样"可见"(没有告警),但缺少它时故障的传播速度是瞬时的。
  5. 五种模式都需要监控:熔断 OPEN 次数、限流拒绝数、重试成功率、线程池队列深度、降级触发次数——这些都是 SLO 的前置指标。
  6. 降级要定期演练。不演练的降级在真正需要时大概率失效。
  7. 限流要分层。单一层次的限流总有盲区——入口、服务间、业务层、数据库层都要有。
  8. 熔断 OPEN 是异常状态。频繁 OPEN 说明下游有结构性问题,需要根因排查。

小结#

分布式可靠性模式的本质是设计失败(Design for Failure)——不是让系统不出错,而是出错时影响可控。冗余防单点、降级保核心、熔断保护上游、限流保护下游、重试兜底瞬时故障、舱壁阻断故障传播。六个模式组成一张安全网,让系统在部分组件失败时仍能对外提供可接受的服务。

判断一个系统的可靠性设计是否成熟,看三件事:核心调用链有熔断+限流、非核心依赖有降级方案、故障不会跨服务扩散(舱壁隔离)。三件事都做到是成熟系统;只做到一两件是"在改进中";一件都没做,系统就是"脆"的——任何时候一个组件抖动都可能引发雪崩。

下一章讲容量规划——可靠性模式的"预防性"补充,提前准备容量避免过载。