值得了解的三种失效概念
欢迎
分布式系统在模式上失败。学会这些模式后,每个postmortem都将成为一种认可练习而不是谜。
关注的内容覆盖了生产故障分析的大部分:
单点故障(SPOF):一个组件的故障会导致整个系统停机。通常隐藏:所有人都依赖的DNS服务器;所有东西都续订的证书;所有的数据库主节点。
级联故障:一个组件的故障触发另一个组件的故障,然后触发另一个组件的故障。一个慢数据库会导致API层面的超时,然后导致重试,然后导致数据库更慢,然后导致更多的超时。爆炸蔓延。
爆炸半径:一个部分失败时,整个系统中会有多少部分停机。架构选择有助于限制或扩大半径。一个SPOF具有无限的爆炸半径。一个防护墙服务具有有限的半径。
在本课结束时,你将:
- 通过检查识别出架构中的SPOF
- 认识到级联故障模式:雷鸣式群起,重试风暴,队列死亡
- 阅读一个真实的时间线并将触发器与触发器暴露的潜在缺陷分开
- 编写无责的行动项目,目标是系统而不是人,覆盖预防/检测/恢复
- 对防护墙和电路断开器作为限制爆炸半径的工具进行推理
发现单点故障
层次化架构检查
考虑一个小型的Web架构:
- DNS: api.example.com -> 一个DNS提供商托管的单个IP地址 203.0.113.10
- CDN: 在 api.example.com 前面有一个单一CDN供应商
- 入口: 一个负载均衡器后面有两个反向代理机器
- 后端: 在两个可用性区域内的六个API副本(每个区域三个)
- 数据库: 在同一个可用性区域内的主数据库和一个只读副本
- 缓存: Redis集群,三个节点分布在两个可用性区域内
问题:哪些组件是SPOF?提示: SPOF不一定是显而易见的'单个机器'类型。一个所有机器都在一个可用性区域内的三台机器是一个区域故障的SPOF。
三种经典级联模式
故障通过依赖关系传播
模式1:雷阵雨。 一个共享资源(缓存、锁定、数据库)失败或重启。依赖于它的每个客户端都会同时重试。重试洪流压倒了重新上线的任何部分;重试比恢复可以吸收的速度更快;恢复永远无法完成。
模式2:重试风暴。 下游服务运行速度变慢。上游调用者,而不是失败,会重试。重试将原始负载乘以多倍。慢服务变慢,触发更多重试。最终负载超过了服务的健康版本。
模式3:死亡队列。 无背压的处理队列收到比它处理得更快的输入。队列无限增长。内存耗尽;消费者崩溃;重新启动;发现一个更大的队列;再次崩溃。
共同点: 一小的初始扰动触发了正反馈循环。系统的响应加剧了失败,而不是减轻它。
减小机制
指数退避加随机波动。 等待重试的客户端每次等待的时间越来越长,且有随机偏差。防止同步重试波浪。
断路器。 调用者跟踪下游故障率。超过阈值,调用者停止调用,进入cool down期,并立即失败自己的请求。防止浪费工作,让下游恢复。
舱壁。 为依赖分隔资源。数据库连接池A,缓存连接池B。慢数据库无法挤压所有连接;缓存调用继续。
负载削减。 当过载时,在边缘放弃请求,而不是接受并缓慢失败。1毫秒内的429比30秒内的500好。
背压。 当消费者无法跟上时,慢生产者。队列变为有限;发送者阻塞;原始工作来源感受到摩擦。
诊断级联
团队的API层在数据库故障转移期间发生熔断。时间线:
- 14:00:00 — 操作员将备库提升为主库。预期不可用时间约为10秒。
- 14:00:08 — 主库不可用。API层开始出现数据库连接错误的请求失败。
- 14:00:08 — API层重试(默认配置:5次重试,无延迟,100ms之间)。
- 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秒)。
DNS SERVFAIL: 两种复合缺陷
真实形状的后勤报告
以下是真实事件的清洁版本。供应商名称更改,IP地址匿名;形状,时间线和教训是真实的。
摘要
网站example.com从所有公共DNS解析器返回SERVFAIL,大约3-4小时。同一DNS主服务器上的其他46个区段未受影响。根因:两种复合缺陷。
1. 供应商A(一个辅助DNS提供商)添加了一个不在主库的allow-axfr-ips白名单中的新内部同步IP。
2. example.com区段有一年多的RFC违反CNAME冲突(demo.example.com在同一标签下有CNAME和MX/TXT记录)导致供应商A拒绝该区段进行新鲜AXFR。
时间线(UTC)
- ~15:00 — 供应商A将新同步IP198.51.100.42添加到他们的基础设施中
- 15:02 — first AXFR-out denied for 198.51.100.42 appears in primary DNS logs (no alerting on this signal)
- ~18:00 — SOA expire window reached; Vendor A drops example.com zone from cache
- ~18:30 — SERVFAIL detected externally
- ~19:45 — root cause identified
- 20:00 — 198.51.100.42 added to allow-axfr-ips; primary restarted
- 20:05 — NOTIFY sent; AXFR initiated; zone STILL SERVFAIL (CNAME conflict)
- 20:07 — check-zone reveals 1 error: CNAME conflict on demo.example.com
- 20:09 — CNAME replaced with A record; zone check clean (0 errors)
- 20:10 — NOTIFY sent; AXFR completes; Vendor A begins serving zone
- 20:11 — dig @8.8.8.8 example.com A returns correct IP — RESOLVED
为什么只有 example.com?
所有 47 个域名共享相同的 DNS 主服务器。受影响的 AXFR IP 块影响了所有域名。但只有 example.com 有 CNAME 冲突,并且只有在拒绝执行时,这个域名需要新的 AXFR。其他域名在拒绝执行之前已经刷新了,或者还没有需要刷新。
潜在缺陷
在 demo.example.com 的 CNAME 冲突已经存在了几年。它可以正常工作是因为主服务器从它的数据库中提供了域名(宽松处理 RFC 违规)以及 Vendor A 从在违规被引入之前从主服务器缓存中获取的陈旧数据。 当 Vendor A 清空了缓存并需要最新数据时,违规才暴露出来。
触发
Vendor A 静默添加了一个新的同步 IP。主服务器的 allowlist 中没有包含它。 AXFR 被拒绝。三小时后(SOA 过期),Vendor A 清空了域名。潜在缺陷在系统尝试恢复时暴露出来。
编写无责行动项
无责:目标系统,不是人
无责行动项目描述的是系统应该如何不同,而不是人应该如何不同。'培训操作员' 是有责的。'在部署之前添加一个自动检查以捕获到这个问题' 是无责的。
好的无责行动项目可以分为三个维度:
- 预防:使坏事变得困难或不可能
- 检测:如果发生了,提前发现
- 恢复:在发生时限制损失
每个项目应该命名(1)具体的系统变更,(2)负责的团队,以及(3)它服务的维度。
不会让整个船沉没的隔舱
从海洋工程借鉴
船舶装有密封隔舱:垂直的墙壁将船舶的船壳分成隔舱。一个隔舱可以发生水淹没有让整艘船沉没;另一个可以失败不会影响其他部分。
分布式系统借用了同一个词和同一个想法。
隔舱模式:根据依赖关系分隔资源。一个服务调用三个下游API使用三个独立的连接池、三个独立的线程池、三个独立的重试预算。一个慢或者失败的下游不能消耗其他两个的资源。
没有隔舱:一个慢的依赖耗尽了共享的线程池;对其他依赖的调用阻塞等待线程;整个服务变得不响应。
有隔舱:一个慢的依赖耗尽了自己的池子;对它的调用快速失败;对其他依赖的调用继续正常;爆炸半径局限在失败的依赖。
集电器断路器
断路器模式:一个对下游依赖的有状态包装,记录故障率。有三个状态:
- 关闭(正常):调用通过。失败次数计算。
- 打开(触发):在过去的30秒内超过故障阈值(比如说50%的故障),断路器打开。调用立即失败,不尝试依赖。避免调用者浪费工作;避免依赖持续接受负载而不健康。
- 半打开(测试):在冷却期间,断路器允许少量的调用通过。如果它们成功,它们关闭回到正常。如果它们失败,它们重新打开另一个冷却。
关键见解:断路器防止在已知不健康期间浪费努力,并给下游一个恢复的机会而不持续负载。
隔舱控制爆炸半径。断路器防止爆炸持续。
控制爆炸半径
您的 API 服务调用了四个下游服务:用户服务、推荐服务、通知服务和第三方支付 API。该团队听说'Recommendation Service'(推荐服务)有时运行不稳定,并希望确保在这种情况下,整个系统都能保持健康。
目前,该服务使用一个共享的 200 个线程的线程池和一个共享的 HTTP 连接池。所有四个下游服务都在竞争这些资源。没有断路器。
设计故障模式审查
合成
您学会了通过检查识别 SPOFs,识别级联故障模式,阅读后事mortem时分离触发器与潜在缺陷,编写无责行动项,跨防御/检测/恢复,使用隔离桶+断路器+优雅退化来限定爆炸半径。
应用所有五个。
您的团队将上线一个新的服务 search.example.com,该服务依赖于三个下游服务:一个主要的搜索索引服务(index.example.com),一个分析服务(analytics.example.com)以及一个推荐服务(recs.example.com)。团队希望您在上线前领导一个'故障模式审查'。
课程下一步
下一步课程
您现在可以识别 SPOF,识别级联,阅读后事mortem时有益于阅读,编写无责行动项,通过设计限定爆炸半径。
本课程的最后一课 (cs_distsys_observability_and_capacity) 教授了如何衡量,以便在用户发现问题之前发现问题。健康检查、版本端点、代理层的四个黄金信号,以及如何将应对洪峰容量的决策与观察到的数据联系起来。
配套课程:geometry_of_failure_modes_and_blast_radius 探讨了中介中心性(哪个图节点是瓶颈)和最小切割(爆炸半径的上界)。
好样的。继续前进。