在網絡通信中,當網絡鏈路發生異常,這將會對系統的可靠性產生重大影響。那么怎么監測通信異常呢?這就是心跳機制。那么異常后怎么處理呢?這就是重連機制。
1、何為心跳
顧名思義, 所謂心跳, 即在 TCP 長連接中, 客戶端和服務器之間定期發送的一種特殊的數據包, 通知對方自己還在線, 以確保 TCP 連接的有效性.
2、心跳實現方式
從技術層面看,要解決鏈路的可靠性問題,必須周期性的對鏈路進行有效性檢測。目前最流行和通用的做法就是心跳檢測。
心跳檢測機制分為三個層面:
1) TCP層面的心跳檢測,即TCP的Keep-Alive機制,它的作用域是整個TCP協議棧;
2) 協議層的心跳檢測,主要存在於長連接協議中。例如SMPP協議;
3) 應用層的心跳檢測,它主要由各業務產品通過約定方式定時給對方發送心跳消息實現。
不同的協議,心跳檢測機制也存在差異,歸納起來主要分為兩類:
1) Ping-Pong型心跳:由通信一方定時發送Ping消息,對方接收到Ping消息之后,立即返回Pong應答消息給對方,屬於請求-響應型心跳;
2) Ping-Ping型心跳:不區分心跳請求和應答,由通信雙方按照約定定時向對方發送心跳Ping消息,它屬於雙向心跳。
心跳檢測策略如下:
1) 連續N次心跳檢測都沒有收到對方的Pong應答消息或者Ping請求消息,則認為鏈路已經發生邏輯失效,這被稱作心跳超時;
2) 讀取和發送心跳消息的時候如果發生了IO異常,說明鏈路已經失效,這被稱為心跳失敗。
無論發生心跳超時還是心跳失敗,都需要關閉鏈路,由客戶端發起重連操作,保證鏈路能夠恢復正常。
3、服務器端實現
心跳監測機制的核心還是超時機制,所謂超時機制就是規定時間內沒有收到心跳包。那么Netty中怎么實現超時檢測的呢?這就是基於IdleStateHandler類。這個類能夠幫助我們實現定時檢測功能,我們先來看看這個類的構造函數。
在構造函數中,定義了三個變量。分別是readIdleTimeSecond,這是讀超時時間。也就是這么長時間沒有讀到數據包,就觸發超時處理機制。第二個參數是writeIdleTimeSecond,這個是寫超時時間。意思就是這么長時間沒有向Socket緩沖區寫數據,就會觸發寫超時處理機制。第三個參數是allIdleTimeSeconds,這就是讀寫事件的超時。在規定時間內沒有讀到或者寫數據,都將出發超時處理機制。
在使用的時候,我們只需要關注這三個參數就行。對於服務器來說,重點關注readIdleTimeSecond,指定時間內沒有讀到Socket緩沖區的數據,就認為異常。對於客戶端來說,可以關注readIdleTimeSecond和writeIdleTimeSecond,或者可以指定allIdleTimeSeconds參數即可。
我們首先寫一個服務端Demo,然后將IdleStateHandler這個處理器添加到Pipeline上面。代碼如下:

我們重點關注兩個地方。首先pipeline上添加IdleStateHandler,我們設置服務器讀超時為5秒鍾。
我們知道,默認的pipeline會將超時事件經過userEventTriggered方法向下面的handler傳播(不熟悉pipeline傳播機制的可以去學習一下)。因為我們添加的其他的handler是用於編解碼和處理TCP半包、粘包的,沒有處理這個事件,那么我們需要在自定義的ServerHandler中進行處理。
我們在這個類中定義了一個內部類ServerHandler。代碼如下:

我們分別實現了讀寫方法(channelRead)、異常機制(exceptionCaught)、超時處理(userEventTriggered)以及客戶端退出通知事件(channelInactive)。
在上面的方法中,我們重點關注超時機制處理。在進入這個userEventTriggered方法后,我們可以根據事件類型進行相應的處理。我們這里服務端重點關注讀超時。這里我們將其 channel關閉。
在實際業務場景中,我們可能采用3次或者n次超時就斷開客戶端channel。這里我們可以加一個變量進行統計。這里不再贅述了。
4、客戶端實現
下面我們來看看客戶端怎么實現超時檢測,以及斷開重連。客戶端這個類叫Client。代碼如下:

和服務端一樣,在設置Bootstrap的時候,在pipeline上添加IdleStateHandler處理器,用於處理超時。接下來,我們看一下重連函數doConnect。

這個重連函數其實就是一個遞歸函數。首先向Bootstrap得到異步事件的future接口。我們向這個接口添加監聽器,假如連接失敗,就向本eventloop的任務隊列中添加一個定時任務(也就是一段代碼)。
當然,實際業務場景下,不可能讓客戶端永無止境的重連。我們可以在遞歸的入口添加一個次數的判斷,達到一定的次數就停止遞歸本方法。
接下來,我們看看ClientHandler類,這里我們也是寫成了一個內部類。為啥這樣寫呢?因為學源碼的。

這里,我們重點關注的是channelInactive()方法。當服務器關閉客戶端channel時,會觸發這個回調方法。那么我們在這個方法中調用外部類的doConnect()方法進行重連。
5、測試
我們首先啟動服務端,檢測到讀超時,關閉客戶端channel。
客戶端這里,檢測到客戶端退出,又啟動重連。

6、其他
到了這里,大概對超時機制檢測和客戶端斷開重連有了一定的了解。在實際業務場景下,可能比這個要復雜一些。比如,我們需要寫一個單獨的消息當作心跳包。其中客戶端發送Ping包,服務器回Pong包。
到了這里,我想起了常用的看門狗軟件,定時去喂狗其實也是定時發送心跳包給看門狗。道理是一樣的,我們需要學會舉一反三。