English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

访客
1 / ?
返回课程列表

值得了解的三个故障概念

欢迎

分布式系统以特定模式发生故障。一旦你掌握了这些模式,每一份事后复盘报告都会变成一种识别练习,而非一场谜团。

三个概念涵盖了生产环境故障分析中最重要的部分:

单点故障 (SPOF):其故障会导致更大系统宕机的组件。通常隐蔽:所有人都依赖的 DNS 服务器;所有服务都向其续期的证书;唯一的数据库主节点。

级联故障:一个组件的故障触发另一个组件的故障,进而触发更多故障。缓慢的数据库导致 API 层超时,超时引发重试,重试进一步加重数据库负载,从而导致更多超时。爆炸范围不断扩散。

爆炸半径:当系统中的一个部件失效时,有多少部分系统会随之宕机。架构选择要么限制要么不限制爆炸半径。单点故障(SPOF)具有无界的爆炸半径。采用舱壁隔离的服务具有有界的爆炸半径。

完成本课后,你将能够:

- 通过检查识别架构中的单点故障(SPOF)

- 识别级联故障模式:惊群效应(thundering herd)、重试风暴(retry storm)、死亡队列(queue of death)

- 阅读真实时间线,并将触发因素与触发因素所暴露的潜在缺陷区分开来

- 编写无指责(blameless)的行动项,针对系统而非个人,涵盖预防 / 检测 / 恢复

- 将舱壁(bulkheads)和断路器(circuit breakers)视为限制爆炸半径的工具进行推理

发现单点故障

分层架构检查

考虑一个小型 Web 架构:

- DNS:api.example.com -> 由一家 DNS 提供商托管的单个名称服务器 IP 203.0.113.10

- CDN:api.example.com 前端仅使用单一 CDN 供应商

- 入口:负载均衡器后方的两台反向代理机器

- 后端:分布在两个可用区中的六个 API 副本(每个可用区三个)

- 数据库:一个主节点 + 一个只读副本,位于同一可用区

- 缓存:Redis 集群,三个节点分布在相同的两个可用区内

问题:哪些组件是单点故障(SPOF)?提示:SPOF 并不总是显而易见的“单台机器”类型。如果三个机器组成的集群全部位于同一个可用区,那么对于该可用区的故障而言,它就是一个 SPOF。

在此架构中识别出至少三个 SPOF。对于每一个,请说明它失效时会导致什么后果,并提出一项具体的更改以消除该 SPOF(无需重写应用程序)。

三种经典级联模式

故障通过依赖关系传播

模式 1:惊群效应(Thundering herd)。 共享资源(缓存、锁、数据库)发生故障或重启。所有依赖该资源的客户端同时发起重试。这股洪流压垮了刚刚恢复的服务;重试请求的堆积速度超过了恢复所能吸收的速度;恢复过程永远无法完成。

模式 2:重试风暴(Retry storm)。 下游服务响应变慢。上游调用方没有选择失败,而是发起重试。重试使原始负载成倍增加。变慢的服务因此更加迟缓,从而触发更多的重试。最终,负载甚至超过了该服务在健康状态下的处理能力。

模式 3:死亡队列(Queue of death)。 一个没有背压机制的处理队列,其接收速度超过了处理速度。队列无限增长。内存耗尽;消费者崩溃;重启后;发现队列变得更大;再次崩溃。

共同点:微小的初始扰动触发了正反馈循环。系统自身的响应放大了故障,而不是将其抑制。

阻尼机制

带抖动的指数退避。 重试的客户端每次等待时间更长,并带有随机偏移量。防止同步的重试浪潮。

熔断器。 调用方跟踪下游的失败率。超过阈值后,调用方在冷却期内停止调用,并立即使自身请求失败。防止无效工作,让下游得以恢复。

舱壁隔离。 按依赖项隔离资源。数据库使用连接池 A,缓存使用独立的连接池 B。缓慢的数据库无法耗尽所有连接;缓存调用继续。

负载丢弃。 当过载时,在边缘丢弃请求,而不是接受它们并缓慢失败。1 毫秒内的 429 优于 30 秒内的 500。

背压。 当消费者无法跟上时,减慢生产者。队列变得有界;发送方阻塞;工作的原始来源感受到摩擦。

级联故障:触发 -> 放大 -> 崩溃,带有阻尼机制

诊断级联故障

一个团队的 API 层在例行数据库故障转移期间崩溃。时间线:

- 14:00:00 — 操作员提升备用数据库为主库。预计不可用时间:约 10 秒。

- 14:00:08 — 主库不可用。API 层请求开始因数据库连接错误而失败。

- 14:00:08 — API 层进行重试(默认配置:5 次重试,无退避策略,间隔 100 毫秒)。

- 14:00:11 — 备用库提升完成,开始接受新连接。

- 14:00:11 — API 层同时建立数千个新的数据库连接(每个副本 × 每个并发请求 × 每次重试)。

- 14:00:13 — 新主库的连接池耗尽;新连接被拒绝。

- 14:00:13-14:05:00 — API 层副本耗尽连接池,抛出异常,崩溃,重启,循环往复。

- 14:05:00 — 操作员手动停止 API 层流量;数据库恢复稳定。

- 14:10:00 — 流量逐步恢复完成。总停机时间:约 10 分钟(预期约为 10 秒)。

识别正在发生的级联故障模式,命名能够防止该故障的阻尼机制(至少两个),并解释为什么从主节点到备用节点的故障转移(本应仅为 10 秒的短暂中断)反而导致了 10 分钟的停机。

DNS SERVFAIL:两个叠加的缺陷

一个真实形态的复盘

以下是某次真实事件的脱敏版本。供应商名称已更改,IP 地址已匿名化;但事件的形态、时间线及经验教训均为真实。

摘要

站点 example.com 在所有公共 DNS 解析器上返回 SERVFAIL,持续约 3-4 小时。同一 DNS 主服务器上的其他 46 个区域均未受影响。根本原因:两个叠加的缺陷。

1. 供应商 A(一家二级 DNS 提供商)添加了一个新的内部同步 IP,该 IP 不在主服务器的 allow-axfr-ips 允许列表中。

2. example.com 区域存在一个多年前的违反 RFC 标准的 CNAME 冲突(demo.example.com 在同一标签下同时拥有 CNAME 和 MX/TXT 记录),导致供应商 A 在新鲜 AXFR 时拒绝该区域。

时间线(UTC)

- ~15:00 — 供应商 A 在其基础设施中添加了新的同步 IP 198.51.100.42

- 15:02 — 主 DNS 日志中首次出现针对 198.51.100.42 的 AXFR-out denied(未对此信号设置告警)

- ~18:00 — 达到 SOA 过期窗口;供应商 A 从缓存中丢弃了 example.com 区域

- ~18:30 — 外部检测到 SERVFAIL

- ~19:45 — 确定根本原因

- 20:00 — 将 198.51.100.42 添加到 allow-axfr-ips;重启主服务器

- 20:05 — 发送 NOTIFY;启动 AXFR;区域仍然 SERVFAIL(CNAME 冲突)

- 20:07 — check-zone 显示 1 个错误:demo.example.com 上的 CNAME 冲突

- 20:09 — CNAME 记录已替换为 A 记录;区域检查通过(0 个错误)

- 20:10 — 已发送 NOTIFY;AXFR 完成;供应商 A 开始提供该区域服务

- 20:11 — dig @8.8.8.8 example.com A 返回正确的 IP — 已解决

为什么只有 example.com 受影响?

所有 47 个区域共享同一个 DNS 主服务器。AXFR IP 封锁影响了所有区域。但只有 example.com 存在 CNAME 冲突,且只有在拒绝访问生效的那一刻,example.com 才需要新的 AXFR。其他区域要么在拒绝访问之前已经刷新,要么当时尚不需要刷新。

潜在缺陷

demo.example.com 处的 CNAME 冲突已存在多年。它之所以能正常工作,是因为主服务器从其数据库提供区域服务(对 RFC 违规较为宽容),而供应商 A 正在使用违规引入之前的陈旧缓存数据提供服务。当供应商 A 丢弃其缓存并需要新数据时,该违规问题便暴露出来。

触发因素

供应商 A 悄悄添加了一个新的同步 IP。主节点的白名单中并未包含该 IP,导致 AXFR 请求被拒绝。三个小时后(SOA 过期时间到达),供应商 A 删除了该区域。当系统尝试恢复时,这一潜在缺陷暴露了出来。

撰写无责行动项

无责原则:针对系统,而非个人

无责行动项应指明系统应如何改进,而非个人应如何改变。“培训操作员”属于有责指责;“添加一项自动化检查,以便在部署前捕获此类问题”则属于无责改进。

优秀的无责行动项通常集中在以下三个维度:

- 预防:使不良情况更难发生或变得不可能

- 检测:若问题发生,能更早地察觉

- 恢复:当问题发生时,将损害降至最低

每项内容应明确:(1)具体的系统变更,(2)负责团队,以及(3)其所属的维度。

针对上述 DNS-SERVFAIL 事后复盘,撰写三条无责(blameless)行动项。将它们分配至预防 / 检测 / 恢复三个维度(每个维度一项)。每项必须指明具体的系统变更及负责团队。请勿将任何个人列为原因。

随船沉没的隔舱

借鉴自船舶工程

船舶配备有防水舱壁:将船体分隔成不同舱室的垂直墙壁。一个舱室进水不会导致整艘船沉没;另一个舱室失效也不会影响其余部分。

分布式系统借用了同样的术语和同样的理念。

舱壁模式:按依赖项隔离资源。一个调用三个下游 API 的服务使用三个独立的连接池、三个独立的线程池和三个独立的重试预算。缓慢或失败的下游无法消耗分配给另外两个下游的资源。

没有舱壁:一个缓慢的依赖项耗尽共享线程池;对其他依赖项的调用因等待线程而阻塞;整个服务变得无响应。

有舱壁:一个缓慢的依赖项耗尽其专属池;对它的调用快速失败;对其他依赖项的调用正常继续;爆炸半径被限制在失败的依赖项范围内。

熔断器

熔断器模式(Circuit breaker pattern):一种围绕下游依赖的状态化包装器,用于跟踪失败率。它包含三种状态:

- 闭合(正常):调用正常通过。失败会被计数。

- 打开(触发):当失败率超过阈值(例如,过去 30 秒内失败率超过 50%)时,熔断器打开。调用会立即失败,而不会尝试访问依赖服务。这避免了调用方浪费工作,也防止了不健康的依赖服务继续承受负载。

- 半开(测试):在冷却期结束后,熔断器允许少量调用通过。如果这些调用成功,熔断器将恢复为闭合状态(正常)。如果失败,它将重新打开并进入下一个冷却期。

关键洞察:熔断器在已知不健康的期间防止了无效努力,& 让下游服务有机会在不持续承受负载的情况下恢复。

舱壁模式(Bulkheads)限制了爆炸半径。熔断器(Circuit breakers)防止爆炸持续自我维持。

限制爆炸半径

你的 API 服务调用了四个下游服务:用户服务(User Service)、推荐服务(Recommendation Service)、通知服务(Notification Service)& 第三方支付 API。团队听说“推荐服务最近有点不稳定”,并希望确保当它失败时,系统的其余部分保持健康。

今天,该服务使用一个包含 200 个线程的单一共享线程池和一个单一共享 HTTP 连接池。所有四个下游服务都在争夺这些资源。目前没有任何熔断器。

请为这个 API 服务提出一个舱壁(Bulkhead)+ 熔断器(Circuit Breaker)的设计方案。请具体说明:你如何在这四个依赖项之间划分线程池/连接池,对于不稳定的推荐服务(Recommendation Service),什么样的熔断器阈值是合理的,以及当推荐服务熔断(open-circuited)时,面向用户的 API 应该做什么?

设计故障模式评审

综合总结

你已学会通过检查识别单点故障(SPOF),识别级联故障模式,在解读事后分析(postmortem)时区分触发因素与潜在缺陷,编写涵盖预防 / 检测 / 恢复的无责行动项,以及通过舱壁隔离 + 熔断器 + 优雅降级来限制爆炸半径。

应用以上全部五项技能。

你的团队即将上线一个新服务 search.example.com,该服务依赖三个下游服务:主搜索索引(index.example.com)、分析服务(analytics.example.com)以及推荐服务(recs.example.com)。团队希望你在上线前主导一次“故障模式评审”。

概述你将主导的故障模式评审。包括:如何揭示单点故障(一种技术)、如何防止搜索服务与其三个下游服务之间的级联故障(两种模式)、针对推荐服务(团队标记为可靠性最低的服务)的一项具体行动项,以及上线时必须到位的监控措施。

本课程后续方向

本课程后续方向

你现在能够识别 SPOF(单点故障),识别级联故障,有效地阅读事后分析报告,撰写无责行动项,并通过设计来限制爆炸半径。

本课程的最后一课(cs_distsys_observability_and_capacity)将教你测量哪些指标,以便在用户发现问题之前提前察觉问题。内容包括健康检查、版本端点、代理层的四个黄金信号,以及突发容量决策如何与观测数据相关联。

配套课程:geometry_of_failure_modes_and_blast_radius 推导了介数中心性(哪个图节点是瓶颈)和最小割(爆炸半径的上限)。

做得很好。继续前进。