長連線的心跳保持設計

JavaDog發表於2019-02-20

前言

TCP有兩種連線場景,長連線和短連線,網路上的通訊沒有真實的連線,只是在通訊雙方保持著連線狀態,通過狀態的變更來表達連線的保持和釋放過程;那什麼是心跳保持,長連線狀態下客戶端和服務端為了知道對方的狀態,需要定時的進行資料傳輸來告訴對方自己還活著,這就是心跳,本文主要講解基於Netty的心跳保持設計。

TCP連線

長連線:

每次通訊後,客戶端和服務端都保持連線狀態,再次通訊時無需新建立連線,好處就是一次連線可以進行多次資料通訊,減小建立連線的開銷,缺點就是需要維持連線狀態,也就是連線保活。

短連線:

每次請求都是三次握手建立連線,請求結束之後四次揮手,釋放連線,優點就是簡單易用,缺點就是每次通訊都需要新建立連線,相比長連線開銷更大。

心跳實現方式

由於長連線下的場景,不是每時每刻都存在資料通訊,那麼如何在資料通訊時最大可能的保證連線的可用性,就需要設計一種對於連線狀態監控重連的手段,也就是連線保活
image.png

那麼實現以上有兩種方式,本文主要討論下應用層面的實現

  • 協議層面,即TCP的keepAlive機制,依賴於作業系統設定,實現偏底層,需要深刻的理解TCP。
  • 應用層面,由應用本身業務邏輯實現,實現更靈活,有更好的可控性。

應用層面的心跳實現

應用層面的實現包含兩部分,客戶端和服務端

基本思路

  • 客戶端在和服務端成功建立連線後本地儲存一個Channel集合
  • 啟動一個定時任務,定時傳送心跳報文到服務端
  • 接收到服務端響應報文之後更新本地Channel的時間
  • 另外一個定時任務判斷Channel的時間是否超過閾值,超過則重連
  • 服務端在收到客戶端的心跳請求之後更新Channel的時間
  • 服務端定時任務判斷如果Channel的時間是否超過閾值,超過則斷開連線

這樣可以實現心跳探測,但是有個缺點就是傳送心跳的定時任務週期性的傳送心跳包,存在大量的無用請求,同時也會增加服務端的壓力,那麼是否存在一種機制客戶端在超過一定時間沒有資料請求的情況下傳送心跳包來探測下服務端是否可用,這樣就減少大部分的無用請求,好在Netty中通過IdleStateHandler類來滿足這樣的需求
image.pngimage.png
我們一般關注的為readerIdleTime,writerIdleTime,allIdleTime,這三個引數
readerIdleTime:當超過設定時間沒有呼叫channelRead()方法則觸發userEventTriggered()方法
writerIdleTime:當超過設定時間沒有呼叫write()方法則觸發userEventTriggered()方法
allIdleTime:是readerIdleTime,writerIdleTime合集
IdleStateHandler的channelActive()方法在socket通道建立時被觸發,從而根據構造引數初始化對應的定時任務,判斷如果對應的方法執行最後一次時間和當前時間比較,如果超過設定的閾值則觸發userEventTriggered()方法
image.png
image.png

客戶端實現

  • NettyClient啟動時將IdleStateHandler加入到pipeline管道中 image.png
  • 自定義類實現ChannelInboundHandlerAdapter,並且重寫userEventTriggered()方法 image.png
  • 收到服務端的響應之後更新Channel的時間 image.png
  • 判斷如果時間間隔超過閾值則重連 image.png以上就完成了客戶端的重連實現,這裡邊有一個地方需要注意的是關於重連的任務不可以放在第二步的userEventTriggered()中實現,因為當服務端down機之後會觸發IdleStateHandler的channelInactive()方法,該方法會取消所有定時任務,就導致重連任務不能夠被執行,所以我們需要單獨啟一個定時任務來處理超時重連邏輯。 image.pngimage.png

服務端實現

  • 接收到客戶端的連線請求之後在本地儲存一個Channel集合
  • 每次接收到心跳資訊之後更新本地Channel的時間 image.png
  • 啟動一個定時任務,判斷如果時間差超過設定的閾值則關閉連線 image.png以上的實現知識基本的連線保持,服務端的心跳可以根據業務場景來調整,比如引入IdleStateHandler,配置readerIdleTime引數來監聽如果超一定閾值則觸發超時判斷,這樣做相比定時任務的執行更節省資源,這裡再提到一個問題就是關於channel的寫回撥判斷,這裡的isSuccess()方法不能用來判斷channel是否可用,isSuccess()方法返回true僅代表訊息寫到TCP 緩衝區成功了而已,(圖片摘自網路),所以我們還是根據判斷channel的最後更新時間來主動關閉連線。 image.png

總結

以上就是基於Netty的長連線心跳保持的基本實現,還有很多細節需要完善,比如連線重試,定時任務的時間間隔判斷,服務端對心跳報文和業務報文的區分等等,都需要隨著業務規模和場景的變化來不斷的完善,同時對於TCP的深刻理解也至關重要。

                                  長連線的心跳保持設計


相關文章