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

un

guest
1 / ?
back to lessons

三個值得了解的失效概念

歡迎

分散式系統會以特定模式失效。一旦你學會這些模式,每份事後分析報告都會變成辨識練習,而非謎題。

三個概念涵蓋了生產環境失效分析中最重要的部分:

單點故障(SPOF):其失效會導致更大系統停機組件。常隱蔽:所有人依賴的 DNS 伺服器;所有東西都向其續期的憑證;單一的資料庫主節點。

連鎖失效:一個元件的故障觸發另一個元件的故障,進而觸發更多元件的故障。緩慢的資料庫導致 API 層出現逾時,進而引發重試,重試又對資料庫造成更大負載,導致更多逾時。爆炸範圍不斷擴大。

爆炸半徑:當系統中某一部分失效時,有多少部分會隨之癱瘓。架構決策會限制或解除對爆炸半徑的約束。單點故障(SPOF)具有無限的爆炸半徑。而採用隔離(Bulkhead)設計的服務則具有有限的爆炸半徑。

完成本課程後,您將能夠:

- 透過檢查識別架構中的單點故障(SPOF)

- 識別連鎖失效模式:雷群效應(thundering herd)、重試風暴(retry storm)、死亡佇列(queue of death)

- 閱讀真實的時間軸,並區分觸發事件與該觸發事件所暴露的潛在缺陷

- 撰寫無責備(blameless)的行動項目,針對系統而非個人,涵蓋預防/偵測/恢復

- 將隔離(bulkheads)與斷路器(circuit breakers)視為限制爆炸半徑的工具進行推理

找出單點故障

分層架構檢查

考慮一個小型的 Web 架構:

- DNS:api.example.com -> 單一網域伺服器 IP 203.0.113.10,由一家 DNS 供應商託管

- CDN:在 api.example.com 前方使用單一 CDN 供應商

- Ingress:負載平衡器後方有兩台反向代理機器

- 後端:在兩個可用區(每個可用區三個)中部署六個 API 副本

- 資料庫:一個主資料庫 + 一個唯讀副本,位於同一個可用區

- 快取:Redis 叢集,三個節點分散在相同的兩個可用區(Availability Zones)內

問題:哪些元件是單點故障(SPOF)?提示:SPOF 不一定是顯而易見的「單台機器」類型。一個由三台機器組成、但全部位於同一個可用區的叢集,對於該可用區的故障而言,就是一個 SPOF。

在此架構中識別至少三個 SPOF。對於每一個,請說明當它故障時會導致什麼後果,並提出一項具體的變更以消除該 SPOF(無需重寫應用程式)。

三種經典級聯模式

失效透過相依性傳播

模式 1:群集效應(Thundering herd)。 共享資源(快取、鎖、資料庫)失效或重新啟動。所有依賴該資源的客戶端同時重試。湧入的請求淹沒了剛恢復的系統;重試累積的速度快於恢復所能吸收的速度;恢復永遠無法完成。

模式 2:重試風暴(Retry storm)。 下游服務變慢。上游呼叫者沒有失敗,而是進行重試。重試使原始負載倍增。緩慢的服務變得更加緩慢,觸發更多重試。最終,負載甚至超過了該服務健康狀態下的處理能力。

模式 3:死亡佇列(Queue of death)。 一個沒有背壓(backpressure)的處理佇列,接收速度大於處理速度。佇列無限增長。記憶體耗盡;消費者崩潰;重新啟動;發現佇列變得更大;再次崩潰。

共同點:微小的初始擾動觸發了正回饋迴路。系統自身的回應放大了失效,而非將其衰減。

衰減機制

帶有抖動(jitter)的指數退避(Exponential backoff)。 重試的客戶端每次等待時間會更長,並加入隨機偏移量。這可防止同步的重試浪潮。

熔斷器(Circuit breaker)。 呼叫端追蹤下游的失敗率。當超過閾值時,呼叫端會停止呼叫一段冷卻時間,並立即讓自己的請求失敗。這可避免浪費資源,並讓下游系統得以恢復。

隔離(Bulkhead)。 依依賴項隔離資源。資料庫使用連線池 A,快取使用獨立的連線池 B。慢速的資料庫無法耗盡所有連線;快取的呼叫仍可繼續。

負載削減(Load shedding)。 當系統過載時,在邊緣直接丟棄請求,而不是接受它們並緩慢失敗。在 1 毫秒內回傳 429 錯誤,優於在 30 秒後回傳 500 錯誤。

背壓(Backpressure)。 當消費者無法跟上時,減慢生產者的速度。佇列變得有界;發送端被阻塞;工作的原始來源會感受到這種阻力。

級聯故障:觸發 -> 放大 -> 崩潰,並附帶衰減機制

診斷級聯故障

某團隊的 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 事後分析,撰寫三項無責備行動項目。將它們分配至預防 / 偵測 / 恢復(每個維度一項)。每個項目必須指明具體的系統變更及負責團隊。請勿將任何個人列為原因。

不會隨船沉沒的艙室

借鑒自船舶工程

船舶配備有密閉艙壁:將船體劃分為多個艙室的垂直牆壁。一個艙室進水不會導致整艘船沉沒;另一個艙室失效也不會影響其餘部分。

分散式系統借用了相同的術語與相同的概念。

艙壁模式:依依賴項隔離資源。一個呼叫三個下游 API 的服務,會使用三個獨立的連線池、三個獨立的執行緒池、三個獨立的重試預算。某個緩慢或失效的下游依賴無法耗盡分配給另外兩個依賴的資源。

沒有艙壁:一個緩慢的依賴耗盡共享的執行緒池;對其他依賴的呼叫會阻塞並等待執行緒;整個服務變得無響應。

有艙壁:一個緩慢的依賴耗盡其自身的池;對它的呼叫快速失敗;對其他依賴的呼叫正常繼續;爆炸半徑被限制在失效的依賴範圍內。

斷路器

熔斷器模式(Circuit breaker pattern):一個有狀態的包裝器,用於封裝下游依賴並追蹤失敗率。它有三種狀態:

- 關閉(正常):請求正常通過。失敗會被計數。

- 開啟(觸發):當失敗率超過閾值(例如,過去 30 秒內失敗率達到 50%)時,熔斷器開啟。請求會立即失敗,而不會嘗試呼叫依賴服務。這避免了呼叫方浪費資源,也防止了不健康的依賴服務繼續承受負載。

- 半開啟(測試):經過一段冷卻期後,熔斷器允許一小部分請求通過。如果這些請求成功,熔斷器會恢復為關閉(正常)狀態。如果失敗,則重新開啟並進入下一次冷卻期。

關鍵洞察:熔斷器在已知不健康的期間防止了無效的嘗試,並讓下游服務有機會在沒有持續負載的情況下恢復。

隔離舱(Bulkheads)限制了爆炸半徑。熔斷器(Circuit breakers)防止爆炸持續自我維持。

限制爆炸半徑

您的 API 服務呼叫了四個下游服務:使用者服務(User Service)、推薦服務(Recommendation Service)、通知服務(Notification Service),以及一個第三方的支付 API。團隊聽說「推薦服務最近有點不穩定」,希望確保當它失敗時,系統的其餘部分仍能保持健康。

今天,該服務使用一個共用的 200 執行緒池以及一個共用的 HTTP 連線池。這四個下游服務都在爭奪這些資源。目前沒有設定任何斷路器(circuit breakers)。

請為這個 API 服務提出一個隔離(bulkhead)+ 斷路器(circuit-breaker)的設計方案。請具體說明:你如何將執行緒池 / 連線池劃分給這四個依賴服務?對於不穩定的推薦服務(Recommendation Service),哪些斷路器閾值是合理的?當推薦服務觸發斷路器(open-circuited)時,面向使用者的 API 應該如何處理?

設計失敗模式審查

綜合

你已學會透過檢查識別單點故障(SPOF)、辨識連鎖失敗模式、在閱讀事後分析報告時區分觸發因素與潛在缺陷、撰寫涵蓋預防/偵測/恢復的無責備行動項目,以及透過隔離區(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 推導介數中心性(哪個圖節點是瓶頸)與最小割(爆炸半徑的上限)。

做得很好。繼續前進。