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