Netty 斷開重連
1. 前言
上節(jié),我們主要講解了 Netty 的心跳檢測機制,其中核心目的是提高性能。本節(jié)我們主要講解的是 Netty 長連接的穩(wěn)定性。
2. 學(xué)習(xí)目的
TCP 協(xié)議下,數(shù)據(jù)是可以雙向傳遞,其實也就是全雙工協(xié)議,通俗點來說就是長連接,只要客戶端和服務(wù)端連接之后,雙方可以正常通行,那么長連接是否存在什么不穩(wěn)定性呢?
在長連接情況下,通常面臨的情況就是網(wǎng)絡(luò)問題,網(wǎng)絡(luò)抖動造成的連接假死,舉個例子:其實客戶端和服務(wù)端的 TCP 連接已經(jīng)斷開,但是雙方?jīng)]有監(jiān)聽到,認為該連接仍然是有效的。
這樣的問題會導(dǎo)致以下幾個后果,如下所示:
- 客戶端往服務(wù)端發(fā)送消息時,由于連接已經(jīng)斷開,會導(dǎo)致請求超時,影響用戶體驗;
- 服務(wù)端往客戶端推送消息時,由于連接已經(jīng)斷開,導(dǎo)致連接推送失??;
- 每條連接都消耗 cpu 和內(nèi)存資源,大量的假死會導(dǎo)致服務(wù)器資源消耗,導(dǎo)致服務(wù)器卡頓甚至宕機。
3. 連接面臨問題及解決方案
4. 連接假死
4.1 產(chǎn)生的原因
連接已經(jīng)斷開,但是程序沒有捕捉的到,認為連接還存在,產(chǎn)生的原因大致如下:
- 應(yīng)用程序內(nèi)部線程堵塞,導(dǎo)致數(shù)據(jù)讀寫也會堵塞;
- 網(wǎng)絡(luò)抖動,數(shù)據(jù)丟包等,發(fā)送方一種發(fā)送不出數(shù)據(jù),接收方也收不到數(shù)據(jù),連接就一直的耗著;
- 公網(wǎng)相對內(nèi)網(wǎng)來說不是很穩(wěn)定,受到的干擾更多,故障的概率也會增大。
4.2 解決辦法
問題: 服務(wù)端 5 秒鐘沒用讀取數(shù)據(jù)事件,那么是否一定是假死呢?
回答: 不一定,主要有兩種情況,①連接假死;②連接空閑。
針對連接假死的解決方案
主要是通過心跳檢測去監(jiān)控,如果指定時間之內(nèi),服務(wù)端沒有收到客戶端的數(shù)據(jù),則主動斷開連接,杜絕了連接假死現(xiàn)象。
針對連接空閑狀態(tài)的解決方案
情況一: 如果對通信的實時性要求不高,并且對性能要求很高的情況,可以直接斷開連接,等待有需要的時候,再重新連接(這個是上節(jié)已經(jīng)講解過了,適合客戶端主動的業(yè)務(wù)場景,比如:IM);
情況二: 如果對通信的實時性要求很高,則不能斷開連接(比如:消息推送),為了保證連接能夠存活而不被心跳檢測機制自動斷開。
針對情況二的解決方案如下:
- 定時發(fā)送空包,并且時間間隔小于心跳檢測時間間隔,保證連接存活;
- 如果連接真的斷開了,則客戶端監(jiān)聽事件 channelInactive () 里面實現(xiàn)斷開重連;
總結(jié),這種模式的好處有兩點,①保證連接能夠長時間存活,避免錯過重要消息;②避免連接空閑時,頻繁的斷開和重連,浪費資源。
其中,心跳檢測上節(jié)以及詳細講解了,這里主要講解一下發(fā)送空包數(shù)據(jù)和斷開重連如何實現(xiàn)。
5. 代碼實現(xiàn)
5.1 服務(wù)端心跳檢測
實例:
ChannelPipeline pipeline = ch.pipeline();
//5秒鐘之內(nèi)沒有 讀事件 則斷開連接
pipeline.addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
//字符串解碼器
pipeline.addLast(new StringDecoder());
//字符串編碼器
pipeline.addLast(new StringEncoder());
//業(yè)務(wù)Handler
pipeline.addLast(new HeartBeatHandler());
代碼說明:
服務(wù)端主要是監(jiān)聽讀事件,每隔 5 秒讀取不到數(shù)據(jù),則認為連接無效,主動斷開連接。
5.2 客戶端定時發(fā)送空包
實例:
public class HeartBeatTimerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//發(fā)送空包
scheduleSendHeartBeat(ctx);
}
private void scheduleSendHeartBeat(ChannelHandlerContext ctx) {
ctx.executor().schedule(() -> {
if (ctx.channel().isActive()) {
//發(fā)送空包(定義一個實體)
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER);
}
}, 3, TimeUnit.SECONDS);
}
}
代碼說明:
- 借助 EventLoop 的定時線程池去實現(xiàn)每隔 3 秒鐘發(fā)送一個空包;
- 空包數(shù)據(jù),自定義一個實體即可;
- 主要的是空包的時間間隔(3s)一定要小于心跳監(jiān)聽的時間間隔(5s)。
5.3 客戶端斷開重連
實例:
//字符串解碼器
pipeline.addLast(new StringDecoder());
//字符串編碼器
pipeline.addLast(new StringEncoder());
//業(yè)務(wù)Handler,需要傳遞“bootstrap”
pipeline.addLast(new ClientHandler(bootstrap));
public class ClientHandler extends ChannelInboundHandlerAdapter {
private Bootstrap bootstrap;
ClientHandler(Bootstrap bootstrap){
this.bootstrap=bootstrap;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
bootstrap.connect("127.0.0.1",80).sync();
}
}
代碼說明:
- 連接斷開時,客戶端的 Handler 的 channelInactive () 會監(jiān)聽的到,在該方法里面實現(xiàn)斷開重連;
- Handler 必須傳遞 bootstrap。
6. 小結(jié)
本節(jié)主要講解了基于心跳檢測的基礎(chǔ)上實現(xiàn)了空包發(fā)送和斷開重連的功能,主要核心意圖有兩個
- 空包發(fā)送: 讓連接能夠長時間的存活,而避免空閑連接收到心跳檢測的干擾;同時還避免了心跳檢測導(dǎo)致的頻繁的斷開和重連,導(dǎo)致資源浪費;
- 斷開重連: 讓連接一直在線,保證了連接的穩(wěn)定性。