本章涵蓋兩個模式:腦裂(Split Brain)隔離(Fencing)


腦裂(Split Brain)#

背景#

在分散式環境中,通常會有一個中央伺服器(Central Server)領導者(Leader) 負責協調工作。當這個領導者故障時,系統必須迅速找到替代者,否則整個系統會快速惡化。

然而,一個核心問題是:我們無法真正確定領導者是永久停止運作,還是僅經歷了一次間歇性故障(Intermittent Failure),例如 Stop-the-World GC 暫停暫時性的網路中斷。無論如何,叢集必須繼續運作並選出新的領導者。

如果原本的領導者只是發生了間歇性故障,我們就會面臨所謂的殭屍領導者(Zombie Leader) 問題——一個被系統判定為已死亡、但實際上已經恢復上線的領導者節點。此時系統中存在兩個活躍的領導者,它們可能會發出互相衝突的命令。

sequenceDiagram
    participant L as 領導者(Leader)
    participant F as 追隨者(Follower)
    participant NL as 新領導者(New Leader)

    L->>F: 1. 定期發送心跳(Heartbeat)
    F-->>L: 確認存活

    Note over L,F: 網路分區發生(Network Partition)

    L-xF: 2. 心跳無法送達
    L-xF: 心跳無法送達

    Note over F: 3. 逾時未收到心跳\n判定領導者已故障

    F->>NL: 4. 追隨者提升自己為新領導者\nLeader Election
    Note over NL: 新領導者開始接受請求

    Note over L: 舊領導者仍認為自己是領導者\n繼續運作中

    Note over L,NL: 5. 腦裂(Split Brain):兩個領導者同時存在\n可能發出互相衝突的命令

定義#

腦裂(Split Brain) 是分散式系統中出現兩個或多個活躍領導者的常見情境。

腦裂問題透過世代時鐘(Generation Clock) 來解決——這是一個單調遞增的數字(Monotonically Increasing Number),用來標示伺服器的世代。

解決方案#

每當選出新的領導者時,世代編號(Generation Number) 就會遞增。例如,如果舊領導者的世代編號是 1,新領導者的世代編號就是 2。這個世代編號會包含在領導者發送給其他節點的每一個請求中。

透過這種方式,節點可以輕鬆地辨別真正的領導者——只需信任擁有最高世代編號的領導者即可。

世代編號應該持久化到磁碟,以確保伺服器重新啟動後仍然可用。一種常見的做法是將世代編號儲存在預寫式日誌(Write-Ahead Log, WAL) 的每一筆記錄中。

範例#

  • Kafka:為了處理腦裂問題(可能出現多個活躍的 Controller Broker),Kafka 使用 Epoch Number,這是一個單調遞增的數字,用來標示伺服器的世代。
  • HDFS:使用 ZooKeeper 確保任何時候只有一個 NameNode 處於活躍狀態。每個 Transaction ID 中都維護一個 Epoch Number,用來反映 NameNode 的世代。
  • Cassandra:使用世代編號(Generation Number) 來區分節點在重新啟動前後的狀態。每個節點儲存一個世代編號,每次節點重新啟動時就會遞增。這個世代編號包含在節點之間交換的 Gossip 訊息中,接收方可以透過比較已知的世代編號與 Gossip 訊息中的世代編號來判斷該節點是否曾經重新啟動。

隔離(Fencing)#

背景#

領導者-追隨者(Leader-Follower) 架構中,當領導者發生故障時,我們無法確定領導者是否真的已經停止運作。例如,緩慢的網路網路分區(Network Partition) 可能觸發新的領導者選舉,即使先前的領導者仍在運行,並且認為自己仍然是活躍的領導者。

在這種情況下,如果系統選出了新的領導者,我們如何確保舊的領導者不會繼續運作並發出互相衝突的命令?

定義#

在先前的領導者周圍設置一道**「隔離牆」(Fence)**,防止它造成損害或導致資料損毀。

解決方案#

隔離(Fencing) 的核心概念是在先前活躍的領導者周圍建立屏障,使其無法存取叢集資源,從而停止服務任何讀寫請求。主要有兩種技術:

  • 資源隔離(Resource Fencing):阻止先前活躍的領導者存取執行必要任務所需的資源。例如,撤銷其對共用儲存目錄的存取權限(通常透過供應商特定的 NFS 命令),或透過遠端管理命令停用其網路連接埠。
  • 節點隔離(Node Fencing):阻止先前活躍的領導者存取所有資源。常見的做法是直接關閉電源重設節點。這是一種非常有效的方法,可以完全阻止該節點存取任何資源。這種技術也被稱為 STONITH(“Shoot The Other Node In The Head”)。
flowchart TD
    Start([偵測到潛在腦裂\n兩個領導者同時存在]) --> Token[檢查隔離令牌\nFencing Token]
    Token --> Compare{比較令牌編號\n單調遞增}
    Compare --> OldLeader[舊領導者:較低的令牌編號\nLower Token]
    Compare --> NewLeader[新領導者:較高的令牌編號\nHigher Token]
    OldLeader --> Reject[儲存服務拒絕舊令牌的請求\nReject Requests with Old Token]
    NewLeader --> Accept[儲存服務接受新令牌的請求\nAccept Requests with New Token]
    Reject --> Fence{選擇隔離方式}
    Fence -->|資源隔離\nResource Fencing| RF[撤銷舊領導者的\n資源存取權限]
    Fence -->|節點隔離\nNode Fencing / STONITH| NF[關閉電源或重設\n舊領導者節點]
    RF --> Safe([系統恢復單一領導者運作])
    NF --> Safe
    Accept --> Safe

資源隔離適用於需要精細控制的場景,而節點隔離則是更為徹底的手段,適用於資源隔離不足以解決問題的情況。

範例#

  • HDFS:使用隔離機制來阻止先前活躍的 NameNode 存取叢集資源,從而防止它繼續服務請求。