1. 程式人生 > >筆記:分布式系統中心跳協議的設計

筆記:分布式系統中心跳協議的設計

處理 格式 實現 失效 答案 字段 負載均衡 協議棧 防止

1 分布式系統中是否需要應用層心跳?

在采用TCP連接作為進程間通信方式的分布式系統中。當任意一方進程意外退出的時候,對方能及時得到連接斷開的通知,操作系統會關閉進程中使用的TCP scoket,會往對方發送FIN分節。keepalive並不是TCP協議規範的一部分,但在幾乎所有的TCP/IP協議棧(不管是Linux還是Windows)中,都實現了keepalive。有了keepalive是不是就不需要應用層心跳了呢?答案是還是需要。心跳除了證明程序還活著之外,更重要的一點是證明程序可以正常工作。keepalive有操作系統處理,當進程發生阻塞或死鎖,操作系統還是可以正常收發keepalive消息,而連接的另一端並不知道進程異常。另外還有幾個異常情況,TCP keepalive 無法解決:

  • 1) 如果操作系統崩潰或機器硬件故障導致機器重啟,則沒有機會發送FIN分節;
  • 2) 如果是網絡故障,連接雙方得知這一情況的唯一方式是檢測心跳超時。

所以為了保證程序一直在正常工作,應用層心跳是必須的。

2 分布式系統中心跳的設計和實現

心跳協議的基本形式是:如果進程C依賴進程S,那S應當規律性向C發送心跳,而C檢查心跳。一般是服務端向客戶端發送心跳,根據實際情況也可以反過來,客戶端向服務端發送心跳。心跳檢測很簡單:如果當前時間與Receiver最後一次收到心跳的時間超過設定的timeout即為超時,判斷Sender心跳失效。分布式系統沒有全局的瞬時狀態,不存在立刻判斷對方出現故障的方法,這是分布式系統的本質困難。
心跳消息應該包含發送方的標識符。建議也包含當前負載,便於客戶端做負載均衡。由於每個程序對“負載”的定義不同,因此心跳消息的格式也就各不相同。我認為可以在某些公共字段的基礎上增加應用程序的特定字段,而不要強行規定全部程序都用相同的心跳消息格式。
如果Sender和Receiver之間有其他消息中轉進程,那麽還應該在心跳消息中加上Sender的發送時間,防止消息在傳輸過程中堆積而導致假心跳。

為了防止偽心跳,在心跳協議的實現上有2關鍵點:

  • 1) 要在工作線程發送,不要單獨起一個“心跳線程”。
  • 2) 與業務消息用同一個連接,不要單獨用“心跳連接”。

對於第1點,這是防止工作線程死鎖或阻塞時還在繼續發心跳。

對於第2點,心跳消息的作用之一是驗證網絡暢通,如果它驗證的不是收發業務數椐的TCP連接暢通,那其意義就大為縮水了。特別要避免用TCP做業務連接,用UDP發送心跳消息,防止一旦TCP業務連接上出現消息堆積而影響正常業務處理時,程序還一如既往地發送UDP心跳,造成客戶端誤認為服務可用。

《Release It》一書第4.1節“The 5 a.m. Problem”講了一個生動的例子,用於描述心跳也是合適的。這個例子說的是Sender和Receiver位於兩個數據中心,之間有網絡防火墻。網絡防火墻的一個特點是會自動檢測TCP死鏈接,即長期沒有消息往來的TCP連接,並清除內存中的連通規則。原來的程序通過單獨的TCP連接發送心跳,與業務數據不在同一 TCP連接。由於心跳始終在周期性地發送,因此,防火墻認為這個TCP連接是活動的。但是業務連接在每天晚t有很長一段時間沒有數據交互,防火墻就判斷其為死鏈接,並且不再轉發此鏈接的IP packet。盡管Sender和Receiver還認為這個TCP業務連接活著,但防火墻實際h已經讓連接斷開了。當每天早上5點鐘第一筆訂單進來的時候,始終會出現超時錯誤,因為業務連接的TCP segment無法到達對方。 TCP協議要經過很長一段時間才能真正判斷連接斷開 (相當於中途斷網,TCP會重試很多次),這時只有重啟一方的進程才能快速修復錯誤。當把心跳消息放到業務連接上之後,問題就迎刃而解了。

筆記:分布式系統中心跳協議的設計