摘要:超過后則認為服務(wù)端出現(xiàn)故障,需要重連。同時在每次心跳時候都用當(dāng)前時間和之前服務(wù)端響應(yīng)綁定到上的時間相減判斷是否需要重連即可??蛻舳藱z測到某個服務(wù)端遲遲沒有響應(yīng)心跳也能重連獲取一個新的連接。
前言
說道“心跳”這個詞大家都不陌生,當(dāng)然不是指男女之間的心跳,而是和長連接相關(guān)的。
顧名思義就是證明是否還活著的依據(jù)。
什么場景下需要心跳呢?
目前我們接觸到的大多是一些基于長連接的應(yīng)用需要心跳來“?;睢?。
由于在長連接的場景下,客戶端和服務(wù)端并不是一直處于通信狀態(tài),如果雙方長期沒有溝通則雙方都不清楚對方目前的狀態(tài);所以需要發(fā)送一段很小的報文告訴對方“我還活著”。
同時還有另外幾個目的:
服務(wù)端檢測到某個客戶端遲遲沒有心跳過來可以主動關(guān)閉通道,讓它下線。
客戶端檢測到某個服務(wù)端遲遲沒有響應(yīng)心跳也能重連獲取一個新的連接。
正好借著在 cim有這樣兩個需求來聊一聊。
心跳實現(xiàn)方式心跳其實有兩種實現(xiàn)方式:
TCP 協(xié)議實現(xiàn)(keepalive 機制)。
應(yīng)用層自己實現(xiàn)。
由于 TCP 協(xié)議過于底層,對于開發(fā)者來說維護性、靈活度都比較差同時還依賴于操作系統(tǒng)。
所以我們這里所討論的都是應(yīng)用層的實現(xiàn)。
如上圖所示,在應(yīng)用層通常是由客戶端發(fā)送一個心跳包 ping 到服務(wù)端,服務(wù)端收到后響應(yīng)一個 pong 表明雙方都活得好好的。
一旦其中一端延遲 N 個時間窗口沒有收到消息則進行不同的處理。
客戶端自動重連先拿客戶端來說吧,每隔一段時間客戶端向服務(wù)端發(fā)送一個心跳包,同時收到服務(wù)端的響應(yīng)。
常規(guī)的實現(xiàn)應(yīng)當(dāng)是:
開啟一個定時任務(wù),定期發(fā)送心跳包。
收到服務(wù)端響應(yīng)后更新本地時間。
再有一個定時任務(wù)定期檢測這個“本地時間”是否超過閾值。
超過后則認為服務(wù)端出現(xiàn)故障,需要重連。
這樣確實也能實現(xiàn)心跳,但并不友好。
在正常的客戶端和服務(wù)端通信的情況下,定時任務(wù)依然會發(fā)送心跳包;這樣就顯得沒有意義,有些多余。
所以理想的情況應(yīng)當(dāng)是客戶端收到的寫消息空閑時才發(fā)送這個心跳包去確認服務(wù)端是否健在。
好消息是 Netty 已經(jīng)為我們考慮到了這點,自帶了一個開箱即用的 IdleStateHandler 專門用于心跳處理。
來看看 cim 中的實現(xiàn):
在 pipeline 中加入了一個 10秒沒有收到寫消息的 IdleStateHandler,到時他會回調(diào) ChannelInboundHandler 中的 userEventTriggered 方法。
所以一旦寫超時就立馬向服務(wù)端發(fā)送一個心跳(做的更完善應(yīng)當(dāng)在心跳發(fā)送失敗后有一定的重試次數(shù));
這樣也就只有在空閑時候才會發(fā)送心跳包。
但一旦間隔許久沒有收到服務(wù)端響應(yīng)進行重連的邏輯應(yīng)當(dāng)寫在哪里呢?
先來看這個示例:
當(dāng)收到服務(wù)端響應(yīng)的 pong 消息時,就在當(dāng)前 Channel 上記錄一個時間,也就是說后續(xù)可以在定時任務(wù)中取出這個時間和當(dāng)前時間的差額來判斷是否超過閾值。
超過則重連。
同時在每次心跳時候都用當(dāng)前時間和之前服務(wù)端響應(yīng)綁定到 Channel 上的時間相減判斷是否需要重連即可。
也就是 heartBeatHandler.process(ctx); 的執(zhí)行邏輯。
偽代碼如下:
@Override public void process(ChannelHandlerContext ctx) throws Exception { long heartBeatTime = appConfiguration.getHeartBeatTime() * 1000; Long lastReadTime = NettyAttrUtil.getReaderTime(ctx.channel()); long now = System.currentTimeMillis(); if (lastReadTime != null && now - lastReadTime > heartBeatTime){ reconnect(); } }IdleStateHandler 誤區(qū)
一切看起來也沒毛病,但實際上卻沒有這樣實現(xiàn)重連邏輯。
最主要的問題還是對 IdleStateHandler 理解有誤。
我們假設(shè)下面的場景:
客戶端通過登錄連上了服務(wù)端并保持長連接,一切正常的情況下雙方各發(fā)心跳包保持連接。
這時服務(wù)端突入出現(xiàn) down 機,那么理想情況下應(yīng)當(dāng)是客戶端遲遲沒有收到服務(wù)端的響應(yīng)從而 userEventTriggered 執(zhí)行定時任務(wù)。
判斷當(dāng)前時間 - UpdateWriteTime > 閾值 時進行重連。
但卻事與愿違,并不會執(zhí)行 2、3兩步。
因為一旦服務(wù)端 down 機、或者是與客戶端的網(wǎng)絡(luò)斷開則會回調(diào)客戶端的 channelInactive 事件。
IdleStateHandler 作為一個 ChannelInbound 也重寫了 channelInactive() 方法。
這里的 destroy() 方法會把之前開啟的定時任務(wù)都給取消掉。
所以就不會再有任何的定時任務(wù)執(zhí)行了,也就不會有機會執(zhí)行這個重連業(yè)務(wù)。
靠譜實現(xiàn)因此我們得有一個多帶帶的線程來判斷是否需要重連,不依賴于 IdleStateHandler。
于是 cim 在客戶端感知到網(wǎng)絡(luò)斷開時就會開啟一個定時任務(wù):
之所以不在客戶端啟動就開啟,是為了節(jié)省一點線程消耗。網(wǎng)絡(luò)問題雖然不可避免,但在需要的時候開啟更能節(jié)省資源。
在這個任務(wù)重其實就是執(zhí)行了重連,限于篇幅具體代碼就不貼了,感興趣的可以自行查閱。
同時來驗證一下效果。
啟動兩個服務(wù)端,再啟動客戶端連接上一臺并保持長連接。這時突然手動關(guān)閉一臺服務(wù),客戶端可以自動重連到可用的那臺服務(wù)節(jié)點。
啟動客戶端后服務(wù)端也能收到正常的 ping 消息。
利用 :info 命令查看當(dāng)前客戶端的鏈接狀態(tài)發(fā)現(xiàn)連的是 9000端口。
:info 是一個新增命令,可以查看一些客戶端信息。
這時我關(guān)掉連接上的這臺節(jié)點。
kill -9 2142
這時客戶端會自動重連到可用的那臺節(jié)點。
這個節(jié)點也收到了上線日志以及心跳包。
現(xiàn)在來看看服務(wù)端,它要實現(xiàn)的效果就是延遲 N 秒沒有收到客戶端的 ping 包則認為客戶端下線了,在 cim 的場景下就需要把他踢掉置于離線狀態(tài)。
消息發(fā)送誤區(qū)這里依然有一個誤區(qū),在調(diào)用 ctx.writeAndFlush() 發(fā)送消息獲取回調(diào)時。
其中是 isSuccess 并不能作為消息發(fā)送成功與否的標(biāo)準(zhǔn)。
也就是說即便是客戶端直接斷網(wǎng),服務(wù)端這里發(fā)送消息后拿到的 success 依舊是 true。
這是因為這里的 success 只是告知我們消息寫入了 TCP 緩沖區(qū)成功了而已。
和我之前有著一樣錯誤理解的不在少數(shù),這是 Netty 官方給的回復(fù)。
相關(guān) issue:
https://github.com/netty/netty/issues/4915
同時感謝 95老徐以及閃電俠的一起排查。
所以我們不能依據(jù)此來關(guān)閉客戶端的連接,而是要像上文一樣判斷 Channel 上綁定的時間與當(dāng)前時間只差是否超過了閾值。
以上則是 cim 服務(wù)端的實現(xiàn),邏輯和開頭說的一致,也和 Dubbo 的心跳機制有些類似。
于是來做個試驗:正常通信的客戶端和服務(wù)端,當(dāng)我把客戶端直接斷網(wǎng)時,服務(wù)端會自動剔除客戶端。
這樣就實現(xiàn)了文初的兩個要求。
服務(wù)端檢測到某個客戶端遲遲沒有心跳過來可以主動關(guān)閉通道,讓它下線。
客戶端檢測到某個服務(wù)端遲遲沒有響應(yīng)心跳也能重連獲取一個新的連接。
同時也踩了兩個誤區(qū),坑一個人踩就可以了,希望看過本文的都有所收獲避免踩坑。
本文所有相關(guān)代碼都在此處,感興趣的可以自行查看:
https://github.com/crossoverJie/cim
如果本文對你有所幫助還請不吝轉(zhuǎn)發(fā)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/73151.html
摘要:缺點可能會導(dǎo)致丟失數(shù)據(jù)在斷開重連的這段時間中,恰好雙方正在通信。博客前端積累文檔公眾號以上參考資料教程理解心跳及重連機制協(xié)議分鐘從入門到精通 showImg(https://segmentfault.com/img/remote/1460000016797888?w=1152&h=720); 在本篇文章之前,WebSocket很多人聽說過,沒見過,沒用過,以為是個很高大上的技術(shù),實際上...
摘要:基礎(chǔ)何為心跳顧名思義所謂心跳即在長連接中客戶端和服務(wù)器之間定期發(fā)送的一種特殊的數(shù)據(jù)包通知對方自己還在線以確保連接的有效性為什么需要心跳因為網(wǎng)絡(luò)的不可靠性有可能在保持長連接的過程中由于某些突發(fā)情況例如網(wǎng)線被拔出突然掉電等會造成服務(wù)器和客戶端的 基礎(chǔ) 何為心跳 顧名思義, 所謂 心跳, 即在 TCP 長連接中, 客戶端和服務(wù)器之間定期發(fā)送的一種特殊的數(shù)據(jù)包, 通知對方自己還在線, 以確保 ...
閱讀 853·2021-08-23 09:46
閱讀 1003·2019-08-30 15:44
閱讀 2655·2019-08-30 13:53
閱讀 3110·2019-08-29 12:48
閱讀 3969·2019-08-26 13:46
閱讀 1887·2019-08-26 13:36
閱讀 3573·2019-08-26 11:46
閱讀 1500·2019-08-26 10:48