值得了解的三個失效概念
歡迎
分散系統在模式上失效。一旦學會模式,每個postmortem都變成認識模式而不是謎語。
三個概念涵蓋了生產失效分析的大部分關鍵部分:
單點失效(SPOF):失效導致更大系統停機的組件。通常隱藏:所有人都依賴的DNS伺服器;所有東西都更新的憑證;所有機器都在同一個可用性區域的單個數據庫主機。
連鎖失效:一個組件失效觸發另一個失效,觸發另一個失效。一個慢的數據庫導致API層面的超時,導致重試,導致數據庫更負擔,導致更多超時。爆炸蔓延。
爆炸半径:當一個部分失效時,系統中有多少部分會失效。架構選擇將爆炸半径束縛或無限擴展。一個SPOF具有無限的爆炸半径。具有分隔壁的服務具有受限的半径。
在這堂課結束時,你將:
- 通過檢查識別架構中的SPOF
- 認出連鎖失效模式:雷霆群,重試風暴,隊列死亡
-閱讀一個真實的時間線並分離觸發器與觸發器揭示的潛在缺陷
- 寫無罪的行動項目,目標系統而不是人,包括預防/檢測/恢復
- 對分隔壁和電路斷開器作為限制爆炸半径工具的推理
找出單點失效
層次架構檢查
考慮一個小型的Web架構:
- DNS:api.example.com -> 單個名伺服器IP 203.0.113.10 由一個DNS提供商主機
- CDN:在api.example.com前面有一個單一CDN供應商
- 入口:在負載平衡器後面的兩個反向代理機器
- 背景:在兩個可用性區域(每個區域三個)中的六個API副本
- 數據庫:一個主數據庫+一個讀副本,在同一個可用性區域
- 缓存:Redis集群,三個節點分佈在同樣的兩個可用性區域
問題:哪些組件是SPOF?提示:SPOF不一定是明顯的‘單機’類型。一個集群的三個機器都在同一個可用性區域是該區域失效的SPOF。
三個經典串聯模式
失敗通過依賴性傳播
模式1:雷陣雨。 一個共享資源(緩存,鎖,數據庫)失效或重新啟動。依賴於它的每個客戶同時重試。重試淹沒了恢復的任何部分;重試超過恢復速度;恢復永遠完成不了。
模式2:重試風暴。 下游服務變慢。上游調用者,沒有失效,則重試。重試增加了原始負載。慢的服務變得更慢,觸發更多重試。最终負載超过了服务的健康版本。
模式3:死亡隊列。 沒有背壓的處理隊列收到比它處理得更快的。隊列無限擴大。記憶體耗盡;消費者崩潰;重新啟動;發現更大的隊列;再次崩潰。
共同點: 一個小的初始干擾觸發了正反饋循環。系統自己的反應放大了故障,而不是減少它。
減振機制
指数退避與抖動。 等待重試的客戶每次等待的時間越來越長,隨機偏移。防止同步重試波浪。
斷路器。 調用者跟踪下游故障率。過了閾值,調用者停止調用,為冷卻期間立即失效自己的請求。防止浪費工作,讓下游恢復。
隔艙。 對每個依賴分隔資源。數據庫連接池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:兩個相互累積的缺陷
一個真實形狀的後mortem
接下來是一個真實事件的清潔版版本。廠商名稱更改,IP地址匿名;形狀、時間軸和教訓都是真實的。
摘要
網站example.com從所有公共DNS解析器返回SERVFAIL約3-4小時。其他46個在相同DNS主機上運行的區域均未受影響。根本原因:兩個相互累積的缺陷。
1. Vendor A(一個次要DNS提供商)將一個新的內部同步IP添加到allow-axfr-ips允許列表之外。
2. example.com區域有一個多年前的RFC違規CNAME衝突(demo.example.com在同一個標籤上有CNAME和MX/TXT記錄)。
這導致Vendor A在新的AXFR中拒絕區域。
時間軸(UTC)
- 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
Why Only example.com?
All 47 zones share the same DNS primary. The AXFR IP block affected all zones. But only example.com had the CNAME conflict, & only example.com needed a fresh AXFR at the moment the deny was enforced. Other zones had already refreshed before the deny or did not yet need to.
Latent defect
The CNAME conflict at demo.example.com had existed for years. It worked because the primary served the zone from its database (lenient about RFC violations) & Vendor A was serving from stale cached data from before the violation was introduced. When Vendor A dropped its cache & needed fresh data, the violation surfaced.
Trigger
Vendor A silently added a new sync IP. The primary's allowlist did not include it. AXFR denied. Three hours later (SOA expire), Vendor A dropped the zone. The latent defect surfaced when the system tried to recover.
Write Blameless Action Items
Blameless: Target Systems, Not People
A blameless action item names something the system should do differently, not something a person should do differently. 'Train the operator' is blameful. 'Add an automated check that catches this before deploy' is blameless.
Good blameless action items cluster into three dimensions:
- Prevention: make the bad thing harder or impossible
- Detection: notice it sooner if it happens
- Recovery: limit the damage when it happens
Each item should name (1) the specific system change, (2) an owner team, & (3) the dimension it serves.
沒有船沉的隔舱
從海軍工程借來的
船舶裝有水密隔艙:垂直的牆壁將船艙分成隔舱。如果一個隔舱水浸沒有使整艘船沉沒,另一個隔舱失效不會影響其他部分。
分佈式系統借用了同一個詞語和相同的想法。
隔艙模式:根據依賴關係分隔資源。一個服務調用三個下游API使用三個不同的連接池、三個不同的線程池和三個不同的重試預算。一個慢或失敗的下游不能消耗其他兩個的資源。
沒有隔艙:一個慢的依賴消耗了共享線程池;對其他依賴的調用阻塞等待線程;整個服務變得不響應。
有隔艙:一個慢的依賴消耗了自己的池;對它的調用立即失敗;對其他依賴的調用繼續正常;失敗的範圍局限於失效的依賴。
斷路器
斷路器模式:一個對下游依賴的有狀態包裝,追蹤失敗率。有三個狀態:
- 關閉(正常):調用通過。失敗數據。
- 打開(觸發):在過去的30秒內失敗率達到門檻(例如,50%失敗),斷路器打開。調用立即失敗,不嘗試依賴。避免浪費工作;避免依賴接收負載時處於不健康狀態。
- 半開(測試):在冷卻期間,允許一小部分調用通過。如果成功,它將返回正常。如果失敗,它將重新打開並再次冷卻。
關鍵見解:斷路器在已知不健康期間防止浪費努力,並給下游一個恢復的機會,而不繼續接收負載。
隔艙限制爆炸半徑。斷路器防止爆炸持續下去。
限制爆炸半徑
您的 API 服務呼叫四個下游服務:User Service、Recommendation Service、Notification Service 和一個第三方 Payment API。團隊已經聽到『推薦服務有點不穩定』,他們希望確保當它失敗時,整個系統仍然健康。
今天這個服務使用一個單一共享的線程池(200個線程)和一個單一共享的 HTTP 連接池。這四個下游服務都在競爭這些資源。沒有任何的電路中斷器(circuit breakers)。
設計一個失效模式檢查
合成
您學會了通過檢查識別 SPOFs,認識出連鎖失效模式,閱讀後勤報告時分離觸發器與潛在缺陷,撰寫無罪行動項目跨防範/偵測/恢復,並使用分隔舱(bulkheads)+ 中斷器(circuit breakers)+ 優雅退化(graceful degradation)限制爆炸半徑。
應用所有五個。
您的團隊將發布一個新的服務 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 介紹了之間的中心性 (哪個圖形節點是瓶頸) 和最小切割 (爆炸半徑的上界)。
好極了。繼續前進。