單點故障盤點
哪些元件一掛、整個系統就跟著掛?把這些點找出來,就是單點故障盤點。高可用的核心是冗餘——每個單點都有替代路徑——但在設計冗餘之前,得先知道單點在哪。盤點的方法是主動反推,而不是等出事後回頭找:假設某個變更已經在 production 造成了事故,倒著推它會怎麼壞、壞了會擴散到哪。這種 pre-mortem 的成本極低(一次結構化討論),卻能在事故發生前就把單點攤開。
Pre-mortem:假設已經出事,反推路徑
Pre-mortem 的核心假設是「這個變更已經在 production 造成事故」,從這個假設反向推導失敗路徑。它分四步走。先列出所有依賴與資料路徑——服務之間的依賴、資料的寫入路徑、對外部的呼叫、以及 schema、config、流量路由這些容易被忽略的隱性依賴。再對每一條路徑問「它斷了、慢了、或回錯了會怎樣」,追蹤影響怎麼擴散到直接依賴方、上游呼叫者、使用者可見的行為、以及資料一致性。接著判斷現有的驗證擋不擋得住這些失敗——CI、壓測、chaos、contract test 有沒有覆蓋,重點是找出「以為有覆蓋、實際上沒有」的那些。最後把識別出的缺口路由出去。
第四步最容易失敗。缺口列了、但沒有 owner、沒有 deadline、沒有路由到下一步,pre-mortem 就淪為一份會議紀錄——問題盤點出來了卻沒人修,跟沒盤點差別不大。上線前補得了的缺口進就緒審查、失敗假設值得驗證的轉成 chaos 實驗、上線前補不了的進技術債追蹤。盤點的價值不在列出多少缺口,在每個缺口都有明確的去處。
依賴 budget:自家可用性被依賴封頂
盤點單點時,一個容易被忽略的單點是外部依賴。自家服務的可用性是所有依賴可用性的乘積——一個 99.9% 的依賴,乘上自家的 99.9%,上限就只剩 99.8%。這意味著關鍵路徑上每多一個沒有替代路徑的依賴,就把自家可用性的天花板往下壓一層。盤點依賴風險不能只看它宣稱的 SLA,要看它失效之後自家還剩多少降級能力、以及失效的 blast radius 有多大。
依賴盤點有三個關鍵訊號要查:關鍵路徑上有沒有「不知道掛了會怎樣」的依賴(這種是最危險的單點,因為連影響都沒摸清)、每個依賴有沒有明確的 failure domain(它的失效會被關在哪個範圍)、以及有沒有 graceful degradation 或 fallback(依賴掛了能不能降級服務而非整個停)。這三個訊號裡,控制面類的依賴風險通常最高——它一掛,可能連恢復動作本身都做不了。
失效模式分類與排序
盤點出來的缺口要分類、排序,不能一視同仁。按失效模式分成四類看得比較清楚:高風險變更缺差異化的驗證 gate、壓測模型沒覆蓋失敗流量(retry 風暴、timeout 級聯、佇列堆積)、rollback 或 DR 路徑事故前沒真的跑過(「有計畫但沒演練」,特徵是 schema 已經不向下相容、failover 的 config 已經漂移)、以及告警延遲或缺失讓使用者比監控先發現。
排序用三個軸:嚴重度(失效的 blast radius 有多大——單服務、跨服務、跨區、還是跨租戶)、發生機率(這條失效路徑多常被觸及)、偵測難度(發現要多久)。三軸相乘,高嚴重度、高機率、又難偵測的缺口最先處理——難偵測特別致命,因為它讓一個高影響的故障可以悄悄壞很久。這裡的陷阱是把評分本身當成目標,維護一套精密評分的成本超過了它帶來的判讀價值,就本末倒置了。
失效局部化:把單點的影響關進小範圍
盤點的目標不只是找出單點,更是把單點失效的影響局部化——限制在最小的可影響範圍內。一個依賴退化,理想的結果不是「整個服務全停」,而是「只有一個 cell 受影響」。做到這點的機制包括把系統切成獨立的 cell(劃出擴散邊界)、用某種分片讓不同用戶的故障不重疊、讓控制面與資料面解耦(控制面掛了資料面還能服務)、以及讓失敗時的工作量保持恆定(避免故障觸發額外負載放大)。
局部化改變了依賴 budget 的算法。當單一依賴的失效被關在一個 cell 裡,budget 算式裡「這個依賴失效」對應的就不是「整個服務全停」,而是「最大可影響一個 cell」——同樣的依賴、同樣的失效機率,影響面小了一個數量級。盤點單點時要問的不只是「它會不會掛」,還有「它掛了的影響能不能被關起來」。
下一步路由
- 找出單點之後,怎麼用冗餘給它替代路徑 → 冗餘設計模式
- 單點掛了怎麼自動切到替代路徑 → Failover 機制
- rollback 與 DR 路徑「有計畫但沒演練」怎麼補 → Disaster recovery 策略
- 依賴的可靠性預算與失效局部化的完整設計 → backend 依賴可靠性預算
#devops #high-availability #spof #pre-mortem #dependency-budget