🎯 什麼情境該想到我
當一個下游服務變慢/掛掉,就把整條鏈路甚至整個系統拖垮(雪崩)時。
⚙️ 怎麼用(組合使用)
- 熔斷(Circuit Breaker):偵測到下游持續失敗就「跳閘」,快速失敗、暫停呼叫,給它時間恢復,避免堆積。
- 艙壁隔離(Bulkhead):把資源(執行緒池/連線)按依賴分艙,一個依賴爆掉不會吃光全部資源。
- 重試 + 退避:對暫時性失敗重試,但要指數退避 + 上限 + 冪等,避免重試風暴(見 工具-MQ消費端防禦三原則)。
- 降級(Fallback):失敗時回傳合理的預設/快取結果,保住核心體驗。
- 流量控制(限流):超過容量就擋,保護系統不被打垮。
★ 為什麼需要它:連鎖故障的機制
下游變慢(不是掛,只是慢)
→ 你的請求堆積,執行緒/連線被佔住
→ ★ 你自己的資源耗盡 → 你也開始變慢
→ 上游的請求在你這裡堆積 → 上游也垮
→ ★ 整條鏈一起倒
★ 最危險的不是「下游掛了」,是「下游變慢」。
掛了會快速失敗(連線被拒),資源立刻釋放;慢會持續佔住資源直到耗盡。
所以逾時是第一道防線,比斷路器更基本。
★ 五種手段的正確順序(由外而內)
限流 Rate Limit ← ★ 最外層:擋掉超過容量的流量,保護自己
艙壁 Bulkhead ← 隔離資源,一個依賴爆掉不影響其他
斷路器 Circuit Breaker ← 下游持續失敗就快速失敗
逾時 Timeout ← ★ 必須在重試外層,否則重試會超出總時限
重試 Retry ← 只對暫時性錯誤
實際呼叫
★ 順序錯了會失效:Retry 包在 Timeout 外面 → 總時間變成 n × timeout,SLO 直接破
(見 裝飾者模式 的 middleware 順序)。
★ 斷路器的三個狀態與參數
失敗率超過閾值
CLOSED ──────────────▶ OPEN ──等待 cooldown──▶ HALF-OPEN
▲ │ │
│ ★ 試探成功 │ ★ 直接快速失敗 │ 放少量請求試探
└─────────────────────┴────────────────────────┘
試探失敗 → 回 OPEN
★ 它本質上是一個狀態機(見 狀態模式)。
| 參數 | 怎麼定 |
|---|---|
| 失敗率閾值 | 常見 50%。★ 要用「比例」不是「絕對次數」,否則低流量時永遠觸發不了 |
| 最小請求數 | ★ 必要:10 次請求裡錯 5 次不該跳閘(樣本太小) |
| cooldown | 幾秒到幾十秒。太短 → 反覆試探打死下游;太長 → 恢復太慢 |
| half-open 的試探量 | 1~數個。不要一次全放行(會瞬間打垮剛恢復的下游) |
★ 「什麼算失敗」要想清楚:逾時、5xx 算;4xx(參數錯)不算(那是你的問題,重試也沒用)。
★ 重試的三條鐵律
| 鐵律 | 說明 |
|---|---|
| ① 只重試暫時性錯誤 | 429、503、逾時、連線失敗 可以;400、401、404、422 絕對不要 |
| ② ★ 必須冪等 | 非冪等的操作重試 = 重複下單、重複扣款。★ 帶冪等鍵(見 工具-MQ消費端防禦三原則) |
| ③ ★ 指數退避 + jitter | 沒有 jitter,所有客戶端會同步醒來再打一次,製造週期性尖峰 |
// ★ full jitter:業界標準做法
delay := time.Duration(rand.Int63n(int64(min(base<<attempt, maxDelay))))★★ 重試放大是連鎖故障最常見的加速器:
三層服務,每層重試 3 次 → 最底層收到 3³ = 27 倍的流量
★ 下游已經在掙扎了,你送 27 倍過去 = 徹底打死
對策:
- ★ 只在最靠近失敗點的那一層重試,其他層直接往上回報
- 重試預算(retry budget):全域限制「重試佔總請求的比例」(如 10%)
- 尊重
Retry-Afterheader
★ 降級要事先設計,不能臨時想
| 降級層次 | 例子 |
|---|---|
| 回傳快取的舊資料 | ★ 最常用。要標示「資料更新於 X」 |
| 回傳預設值 / 空結果 | 推薦系統掛了 → 回熱門商品 |
| 關閉非核心功能 | 購物車能結帳,但「你可能也喜歡」消失 |
| ★ 排隊 / 非同步化 | 同步做不到 → 收下請求、回 202、之後處理 |
| 拒絕 | ★ 明確的錯誤 + Retry-After,遠勝於超時等待 |
★ 降級開關要能手動觸發(feature flag),不要只靠自動偵測——
事故當下你需要能立刻關掉某個功能,而不是等斷路器自己判斷。
★ 艙壁隔離
❌ 共用一個執行緒池/連線池
→ 呼叫 A 服務的請求全部卡住 → 池子耗盡 → ★ 呼叫 B 服務的也沒資源了
✅ 每個下游依賴各自一個池(或 semaphore 限制併發數)
→ A 爆掉只影響 A 那一艙
★ Go 裡最簡單的實作就是每個依賴一個 semaphore(golang.org/x/sync/semaphore 或帶緩衝的 channel)。
🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★★ 逾時是第一優先,不是斷路器。 沒有逾時,其他手段全部失效——因為資源永遠不會被釋放。
而且逾時要端到端傳遞(context),不是每層各設一個(見 工具-select超時與競速)。 - ★ 重試放大是連鎖故障的加速器(見上)。只在一層重試。
- ★ 斷路器在「低流量」時不可靠。一小時只有 20 次請求時,失敗率的統計意義很弱。
→ 設最小請求數,或對低流量服務不用斷路器。 - ★ 降級的品質要能被察覺。默默回傳空結果 → 使用者以為「真的沒有資料」。
要有 metric 追蹤降級觸發率,而且降級中的回應要能被識別(header 或欄位標示)。 - 這些機制本身會製造 bug。斷路器誤跳、重試造成重複、降級邏輯有 bug——
★ 每一個容錯機制都要被測試(故障注入,見 工具-注入故障與韌性演練)。 - 不要對所有依賴都套用全套。核心依賴掛了就是該失敗;只有「可以沒有」的依賴才值得降級。
→ 先分清楚關鍵路徑與非關鍵路徑。 - 用成熟的函式庫(
sony/gobreaker、failsafe-go、Envoy/Istio 的 outlier detection),不要自己寫。
🔗 相關工具
- 工具-select超時與競速 —— ★ 逾時必須沿 context 端到端傳遞
- 工具-故障偵測 —— 「掛了還是只是慢」與健康檢查
- 工具-MQ消費端防禦三原則 —— 冪等與重試
- 工具-注入故障與韌性演練 —— 這些機制要被測試
- 裝飾者模式 —— middleware 的順序原則
- 狀態模式 —— 斷路器是經典狀態機
- 工具-生產故障反模式清單 —— 連鎖故障的真實案例
- 工具-限流器設計 —— 最外層的保護