🎯 什麼情境該想到我

當分散式系統需要判斷「某個節點還活著嗎」——但你分不清它是真的掛了還是只是網路慢/GC 停頓時。

⚙️ 怎麼用

  1. 心跳 / 逾時:最簡單——定期回報,逾時未回報視為可疑。缺點:逾時值難調(太短易誤判、太長反應慢)。
  2. Gossip 式偵測:節點互相傳播彼此的存活資訊,避免單點誤判、擴展性好。
  3. Phi-accrual 偵測器:不給「死/活」的布林,而是輸出一個懷疑程度(機率),讓上層依情境自訂閾值,對網路抖動更穩健。

核心體悟:在分散式下,你永遠無法確定一個節點是「掛了」還是「只是慢」——只能給機率、做取捨。

★ 根本難題:你無法區分「掛了」和「很慢」

送出請求 → 沒有回應
可能是:① 對方掛了      ② 對方很慢      ③ 請求在路上丟了
        ④ 回應在路上丟了 ⑤ 對方 GC 停頓   ⑥ 你自己的網路壞了
★ 從外面看,這六種完全無法區分。

★ 這不是工程問題,是理論限制(非同步系統中不可能有完美的故障偵測器)。
所以所有故障偵測都是「在誤判與延遲之間選一個位置」,沒有正確答案。

逾時太短逾時太長
★ 誤判:把健康但暫時慢的節點踢掉★ 反應慢:故障節點繼續接流量
→ 觸發不必要的 failover / 再平衡→ 使用者持續看到錯誤
→ ★ 在高負載時特別危險(慢 → 被踢 → 剩下的更慢 → 全部被踢)

★ 「高負載時的誤判」是連鎖故障最常見的起點——這是設定逾時時最該記住的一件事。

★ Phi-accrual:輸出「懷疑度」而不是布林

傳統心跳:超過 5 秒沒回應 → 死了(硬閾值,非黑即白)

Phi-accrual:

① 記錄過去 N 次心跳的「間隔」,建立分布(通常假設常態或指數分布)
② 依「距離上次心跳多久」算出 φ = -log₁₀(P(還活著))
   φ = 1  → 誤判機率 10%
   φ = 2  → 誤判機率 1%
   ★ φ = 8  → 誤判機率 10⁻⁸
③ ★ 不同的上層各自選閾值:
   「把它移出讀取池」用 φ > 3(寬鬆,可逆,代價低)
   「觸發 failover」  用 φ > 8(嚴格,不可逆,代價高)

★ 它真正的價值有兩個:

  1. 自適應:網路平時抖就自動放寬,平時穩就自動收緊——不用手調逾時值
  2. ★ 讓「代價不同的決策」用不同的閾值 —— 這才是關鍵。
    「暫時不送流量過去」和「宣告它死亡並重新選主」不該用同一個判斷。

(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。

🔗 相關工具