當前位置: 華文世界 > 科技

TCP 已經實作 KeepAlive, 為什麽套用層還要實作一遍?

2024-08-28科技
  • TCP 心跳
  • 原理簡述
  • 相關參數
  • 局限性
  • 套用層心跳
  • 必要性
  • 實作方式
  • 實作細節
  • 1. 不要單獨實作 「心跳執行緒」
  • 2. 不要單獨實作 「心跳連線」
  • 擴充套件閱讀
  • TCP 心跳

    TCP Keepalive 是一種用於檢測 TCP 連線是否活躍的機制,透過定期發送探測封包來確定連線的狀態,主要用於檢測空閑 (僵屍) 連線、保持 NAT 對映 (NAT 器材、防火墻器材) 等。

    原理簡述

    1. 要啟用 TCP Keepalive 自動檢測機制,需要通訊雙方都開啟 Keepalive 選項
    2. 如果在一定時間(預設 2 小時)內沒有數據傳輸,TCP 會發送一個 Keepalive 探測封包
    3. 如果通訊的對方仍然活躍,就會對該探測封包進行響應,如果對方沒有響應,TCP 將重試發送探測封包
    4. 在達到最大重試次數(預設 10 次)後,如果仍然未收到響應,TCP 將認為連線已斷開,關閉連線

    下面是根據 TCP Keepalive 工作原理,轉換後的邏輯偽代碼 (針對單個 TCP 連線)。

    # TCP Keepalive 控制參數KEEPALIVE_INTERVAL = 7200 # 預設 2 小時KEEPALIVE_PROBES = 10 # 預設 10 次KEEPALIVE_TIMEOUT = 75 # 預設 75 秒# 開啟 TCP Keepalive 機制# 初始化各項控制參數def enable_keepalive(socket): socket.setsockopt(..., socket.SO_KEEPALIVE, 1) socket.setsockopt(..., KEEPALIVE_INTERVAL) socket.setsockopt(..., KEEPALIVE_PROBES) socket.setsockopt(... KEEPALIVE_TIMEOUT)# 如果連線在 Keepalive 間隔時間內處於空閑狀態# 發送 Keepalive 探測包並啟動探測計時器def send_keepalive_probe(socket): if is_idle(socket, KEEPALIVE_INTERVAL): send_probe_packet(socket) start_probe_timer(socket)# 處理 Keepalive 響應# 如果收到探測包的 ACK 確認,則重設空閑計時器# 否則增加探測次數# 如果超過最大探測次數,則關閉連線# Function to handle keepalive responsedef handle_keepalive_response(socket): if received_probe_ack(socket): reset_idle_timer(socket) else: increment_probe_count(socket) if probe_count(socket) > KEEPALIVE_PROBES: close_connection(socket)# 檢查Keepalive超時# 如果探測計時器過期,則處理 Keepalive 響應def check_keepalive_timeout(socket): if probe_timer_expired(socket): handle_keepalive_response(socket)# 核心主迴圈管理 Keepalive# 1. 啟用 Keepalive 選項# 2. 在連線開啟時定期發送探測包和檢查超時# Main loop to manage keepalivedef manage_keepalive(socket): enable_keepalive(socket) while is_open(socket): send_keepalive_probe(socket) check_keepalive_timeout(socket) time.sleep(KEEPALIVE_TIMEOUT) ......

    相關參數

    Linux 內核中和 TCP Keepalive 機制相關的幾個參數如下:

  • tcp_keepalive_time:首次探測之前的空閑時間(預設 2 小時)
  • tcp_keepalive_intvl:重試探測的時間間隔(預設 75 秒)
  • tcp_keepalive_probes:最大重試次數(預設 10 次)
  • 當然,這些參數都可以透過修改系統設定檔進行修改,尤其在最佳化高並行場景和移動場景為主的後端伺服器時,這幾個參數需要著重最佳化一下:

    # 設定首次探測之前的空閑時間為 10 分鐘echo 600 > /proc/sys/net/ipv4/tcp_keepalive_time# 設定重試探測的時間間隔為 15 秒echo 15 > /proc/sys/net/ipv4/tcp_keepalive_intvl# 設定最大重試次數為 3 次echo 3 > /proc/sys/net/ipv4/tcp_keepalive_probes

    執行 sysctl -p 命令生效,重新開機之後仍然有效。

    局限性

    TCP Keepalive 機制由內核 (作業系統) 負責執行,當行程結束後,內核會針對行程中未關閉的連線逐個進行關閉 (向連線的通訊對方發送 FIN 報文),這樣就保證了每個連線的通訊雙方都可以知道通訊的狀態,並根據狀態來完成不同的具體業務邏輯。

    表面上看,不論行程是執行還是結束,TCP Keepalive 機制都可以透過內核很好地完成,但是在一些極端場景中,內核無法保證 TCP 協定棧正常工作,例如:

  • 作業系統異常導致重新開機,TCP 協定棧沒有機會發送 FIN 報文
  • 伺服器硬件故障、基礎設定故障 (如斷電、斷網、地理不可抗力因素),TCP 協定棧同樣沒有機會發送 FIN 報文
  • 海量並行連線數,作業系統或行程重新開機時,TCP 協定棧可能無法斷開所有連線,也就是 FIN 報文出現丟包後,沒有更多的時間進行重試
  • 網絡鏈路故障,只能等到 TCP Keepalive 檢測超時,通訊雙方才能確認這種情況,此時距離發生故障可能已經過去了一段時間
  • 套用層心跳

    必要性

    前文中講到了 TCP Keepalive 機制 (內核實作) 的局限性,除此之外,結合到套用層一起來看的話, TCP Keepalive 機制無法確認套用層的心跳檢測目標:應用程式還在正常工作 。具體來說,TCP Keepalive 檢測結果正常,只能說明兩件事情:

    1. 應用程式 (行程) 還存在
    2. 網絡鏈路正常

    但是 當應用程式行程執行中發生異常時 ,例如死結、Bug 導致的無限迴圈、無限阻塞 等,雖然此時作業系統依然可以正常執行 TCP Keepalive 機制,但是對於應用程式的異常情況,通訊對方是無法得知的。

    此外,套用層心跳檢測具有更好的靈活性,例如可以控制檢測時間、間隔、例外處理機制、附加額外數據等。

    綜上所述,套用層心跳檢測是必須實作的。

    實作方式

    常見的套用層心跳實作方式有:

  • HTTP: 存取指定 URL, 根據響應碼或者響應數據來判定套用是否正常
  • Exec: 執行指定 (Shell) 命令 (例如檔檢查、網絡檢查),並檢查命令的結束狀態碼,如果狀態碼為 0,說明套用正常執行
  • WebSocket: 和 HTTP 檢測方式類似
  • 其他自訂檢測方式
  • 其中業界主流的檢測方式是 HTTP (長連線方式), 主要是因為:

    1. HTTP 實作簡單,基於長連線的方式避免了連線的建立和釋放帶來的開銷
    2. HTTP 對於 (異構) 環境的要求很低,而且大多數套用中都使用 HTTP 作為 API 主要通訊協定,心跳檢測並不會帶來多少額外的工作量

    實作細節

    1. 不要單獨實作 「心跳執行緒」

    使用單獨的執行緒來實作 「心跳檢測」,雖然可以將心跳檢測套用程式碼和具體的業務邏輯程式碼隔離,但是當 「業務執行緒」 發生死結或者 Bug 崩潰時,心跳執行緒檢測不到。

    所以應該將心跳檢測直接實作在 「業務執行緒」 中。

    2. 不要單獨實作 「心跳連線」

    對於網絡 (例如 TCP) 編程的場景,心跳檢測應該在 「業務連線」 直接實作,而不是使用單獨的連線,這樣當業務連線出現異常時,通訊對方可以第一時間感知到 (沒有及時收到心跳響應)。

    此外,大多數網絡防火墻會定時監測空閑 (僵屍) 連線並清除,如果心跳檢測使用額外的連線,那麽當 「業務連線」 長時間沒有要發送的數據時,就已經被防火墻斷開了,但是此時心跳檢測連線還在正常工作,這會影響通訊對方的判斷,以為 「業務連線」 還在正常工作。

    所以應該將心跳檢測直接實作在 「業務連線」 中。