🎯 什麼情境該想到我
當分散式系統需要判斷「某個節點還活著嗎」——但你分不清它是真的掛了還是只是網路慢/GC 停頓時。
⚙️ 怎麼用
- 心跳 / 逾時:最簡單——定期回報,逾時未回報視為可疑。缺點:逾時值難調(太短易誤判、太長反應慢)。
- Gossip 式偵測:節點互相傳播彼此的存活資訊,避免單點誤判、擴展性好。
- Phi-accrual 偵測器:不給「死/活」的布林,而是輸出一個懷疑程度(機率),讓上層依情境自訂閾值,對網路抖動更穩健。
核心體悟:在分散式下,你永遠無法確定一個節點是「掛了」還是「只是慢」——只能給機率、做取捨。
★ 根本難題:你無法區分「掛了」和「很慢」
送出請求 → 沒有回應
可能是:① 對方掛了 ② 對方很慢 ③ 請求在路上丟了
④ 回應在路上丟了 ⑤ 對方 GC 停頓 ⑥ 你自己的網路壞了
★ 從外面看,這六種完全無法區分。
★ 這不是工程問題,是理論限制(非同步系統中不可能有完美的故障偵測器)。
所以所有故障偵測都是「在誤判與延遲之間選一個位置」,沒有正確答案。
| 逾時太短 | 逾時太長 |
|---|---|
| ★ 誤判:把健康但暫時慢的節點踢掉 | ★ 反應慢:故障節點繼續接流量 |
| → 觸發不必要的 failover / 再平衡 | → 使用者持續看到錯誤 |
| → ★ 在高負載時特別危險(慢 → 被踢 → 剩下的更慢 → 全部被踢) |
★ 「高負載時的誤判」是連鎖故障最常見的起點——這是設定逾時時最該記住的一件事。
★ Phi-accrual:輸出「懷疑度」而不是布林
傳統心跳:超過 5 秒沒回應 → 死了(硬閾值,非黑即白)
Phi-accrual:
① 記錄過去 N 次心跳的「間隔」,建立分布(通常假設常態或指數分布)
② 依「距離上次心跳多久」算出 φ = -log₁₀(P(還活著))
φ = 1 → 誤判機率 10%
φ = 2 → 誤判機率 1%
★ φ = 8 → 誤判機率 10⁻⁸
③ ★ 不同的上層各自選閾值:
「把它移出讀取池」用 φ > 3(寬鬆,可逆,代價低)
「觸發 failover」 用 φ > 8(嚴格,不可逆,代價高)
★ 它真正的價值有兩個:
- 自適應:網路平時抖就自動放寬,平時穩就自動收緊——不用手調逾時值
- ★ 讓「代價不同的決策」用不同的閾值 —— 這才是關鍵。
「暫時不送流量過去」和「宣告它死亡並重新選主」不該用同一個判斷。
(Cassandra、Akka 用它。)
★ 健康檢查的三個層次(常被混為一談)
| 檢查 | 問的問題 | 失敗時該做什麼 |
|---|---|---|
| Liveness | 「行程還活著嗎?」 | ★ 重啟它 |
| Readiness | 「現在能接流量嗎?」(暖機中、依賴掛了、過載) | ★ 移出負載平衡,但不要重啟 |
| Startup | 「啟動完成了嗎?」 | 延後前兩者的檢查 |
★★ 最常見也最危險的錯誤:liveness 檢查裡去查資料庫。
資料庫短暫不可用 → 所有 pod 的 liveness 失敗 → ★ 全部被重啟 → 重啟風暴 → 連線風暴 → 資料庫更掛 → 永遠起不來Liveness 只該檢查「這個行程本身是不是卡死了」(例如 deadlock)——
不要檢查任何外部依賴。 外部依賴放 readiness。
Readiness 也要小心:如果所有實例都因為同一個下游而 not ready → 整個服務從 LB 消失。
→ 考慮「下游掛了仍回報 ready,但以降級模式服務」(見 工具-服務容錯設計)。
★ 逾時值該怎麼定(不要拍腦袋)
① 量出這個呼叫的延遲分布(p50 / p95 / p99 / p999)
② 逾時 ≈ p99 × 1.5~2 ← ★ 從資料來,不是從直覺來
③ ★ 確認「逾時 × 重試次數 < 上游的逾時」
否則上游早就放棄了,你還在重試 —— 純浪費資源
④ 逾時必須配合 context 往下傳(見 [[工具-select超時與競速]])
★ 一個常見的錯誤配置:
每一層各設 30 秒逾時 + 3 次重試 → 最壞情況 3 × 3 × 3 = 27 次請求、270 秒。
這在故障時會放大流量數十倍,把半掛的下游徹底打死。
→ 正解:端到端一個 deadline,往下傳遞(見 工具-服務容錯設計)。
★ 偵測之後要做什麼(比偵測本身更重要)
| 判定 | 行動 | 可逆嗎 |
|---|---|---|
| 可疑 | ★ 降低權重(少送一點流量過去) | ✅ |
| 大概掛了 | 移出負載平衡池 | ✅ |
| 確定掛了 | 觸發 failover / 再平衡 | ❌ 不可逆,要更嚴格的閾值 |
★ 設計原則:可逆的行動用寬鬆閾值,不可逆的用嚴格閾值。
而且要有「一次最多踢掉幾個節點」的上限——
否則一次網路抖動可能讓整個叢集自我毀滅。
🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★★ Liveness 檢查不要碰外部依賴(見上)。這是 Kubernetes 環境最常見的自殺方式。
- ★ 誤判的代價往往大於延遲的代價。把健康節點踢掉 → 剩下的負載更高 → 更多節點被判定為慢 → 雪崩。
→ 一定要有「同時最多能移除 N%」的保護(Kubernetes 的 PDB、負載平衡器的 panic threshold)。 - ★ 節點「半死」比「全死」更糟。全死了流量會自動繞過;半死(能回應健康檢查但實際處理失敗)
會持續吸走流量。→ 健康檢查要檢查「真的能做事」,不是只回 200。 - 不要用單一觀察者判斷。單一節點的網路問題會讓它誤判所有人都掛了。
→ gossip 式的多方確認(見 工具-反熵與Gossip傳播)。 - ★ GC 停頓與 VM 暫停和「網路分區」無法區分(見 工具-線性一致性與共識)。
所以「偵測到掛了」之後的行動必須是安全的(fencing token)。 - 健康檢查本身會有成本。幾千個節點互相檢查 → 的流量。gossip 是為此設計的。
- 時鐘偏移會讓心跳判斷失準。用單調時鐘計算間隔,不要用 wall clock。
🔗 相關工具
- 工具-服務容錯設計 —— 偵測到之後的降級、重試、斷路器
- 工具-反熵與Gossip傳播 —— 多方確認的故障偵測
- 工具-線性一致性與共識 —— 「掛了還是只是慢」的理論限制與 fencing
- 工具-資料複製 —— failover 的三個坑
- 工具-select超時與競速 —— 逾時必須沿 context 往下傳
- 工具-遙測與監控 —— 逾時值要從延遲分布來
- 工具-生產故障反模式清單 —— 誤判造成的連鎖故障
- 設計資料密集型應用