亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Netty 斷開重連

1. 前言

上節,我們主要講解了 Netty 的心跳檢測機制,其中核心目的是提高性能。本節我們主要講解的是 Netty 長連接的穩定性。

2. 學習目的

TCP 協議下,數據是可以雙向傳遞,其實也就是全雙工協議,通俗點來說就是長連接,只要客戶端和服務端連接之后,雙方可以正常通行,那么長連接是否存在什么不穩定性呢?

在長連接情況下,通常面臨的情況就是網絡問題,網絡抖動造成的連接假死,舉個例子:其實客戶端和服務端的 TCP 連接已經斷開,但是雙方沒有監聽到,認為該連接仍然是有效的。

這樣的問題會導致以下幾個后果,如下所示:

  1. 客戶端往服務端發送消息時,由于連接已經斷開,會導致請求超時,影響用戶體驗;
  2. 服務端往客戶端推送消息時,由于連接已經斷開,導致連接推送失??;
  3. 每條連接都消耗 cpu 和內存資源,大量的假死會導致服務器資源消耗,導致服務器卡頓甚至宕機。

3. 連接面臨問題及解決方案

圖片描述

4. 連接假死

4.1 產生的原因

連接已經斷開,但是程序沒有捕捉的到,認為連接還存在,產生的原因大致如下:

  1. 應用程序內部線程堵塞,導致數據讀寫也會堵塞;
  2. 網絡抖動,數據丟包等,發送方一種發送不出數據,接收方也收不到數據,連接就一直的耗著;
  3. 公網相對內網來說不是很穩定,受到的干擾更多,故障的概率也會增大。

4.2 解決辦法

問題: 服務端 5 秒鐘沒用讀取數據事件,那么是否一定是假死呢?

回答: 不一定,主要有兩種情況,①連接假死;②連接空閑。

針對連接假死的解決方案

主要是通過心跳檢測去監控,如果指定時間之內,服務端沒有收到客戶端的數據,則主動斷開連接,杜絕了連接假死現象。

針對連接空閑狀態的解決方案

情況一: 如果對通信的實時性要求不高,并且對性能要求很高的情況,可以直接斷開連接,等待有需要的時候,再重新連接(這個是上節已經講解過了,適合客戶端主動的業務場景,比如:IM);
情況二: 如果對通信的實時性要求很高,則不能斷開連接(比如:消息推送),為了保證連接能夠存活而不被心跳檢測機制自動斷開。

針對情況二的解決方案如下:

  1. 定時發送空包,并且時間間隔小于心跳檢測時間間隔,保證連接存活;
  2. 如果連接真的斷開了,則客戶端監聽事件 channelInactive () 里面實現斷開重連;

總結,這種模式的好處有兩點,①保證連接能夠長時間存活,避免錯過重要消息;②避免連接空閑時,頻繁的斷開和重連,浪費資源。

其中,心跳檢測上節以及詳細講解了,這里主要講解一下發送空包數據和斷開重連如何實現。

5. 代碼實現

5.1 服務端心跳檢測

實例:

ChannelPipeline pipeline = ch.pipeline();
//5秒鐘之內沒有 讀事件 則斷開連接
pipeline.addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));

//字符串解碼器
pipeline.addLast(new StringDecoder());

//字符串編碼器
pipeline.addLast(new StringEncoder());

//業務Handler
pipeline.addLast(new HeartBeatHandler());

代碼說明:

服務端主要是監聽讀事件,每隔 5 秒讀取不到數據,則認為連接無效,主動斷開連接。

5.2 客戶端定時發送空包

實例:

public class HeartBeatTimerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //發送空包
        scheduleSendHeartBeat(ctx);
    }

    private void scheduleSendHeartBeat(ChannelHandlerContext ctx) {
        ctx.executor().schedule(() -> {
            if (ctx.channel().isActive()) {
                //發送空包(定義一個實體)
                ctx.writeAndFlush(Unpooled.EMPTY_BUFFER);
            }

        }, 3, TimeUnit.SECONDS);
    }
}

代碼說明:

  1. 借助 EventLoop 的定時線程池去實現每隔 3 秒鐘發送一個空包;
  2. 空包數據,自定義一個實體即可;
  3. 主要的是空包的時間間隔(3s)一定要小于心跳監聽的時間間隔(5s)。

5.3 客戶端斷開重連

實例:

//字符串解碼器
pipeline.addLast(new StringDecoder());
//字符串編碼器
pipeline.addLast(new StringEncoder());
//業務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();
    }
}

代碼說明:

  1. 連接斷開時,客戶端的 Handler 的 channelInactive () 會監聽的到,在該方法里面實現斷開重連;
  2. Handler 必須傳遞 bootstrap。

6. 小結

本節主要講解了基于心跳檢測的基礎上實現了空包發送和斷開重連的功能,主要核心意圖有兩個

  1. 空包發送: 讓連接能夠長時間的存活,而避免空閑連接收到心跳檢測的干擾;同時還避免了心跳檢測導致的頻繁的斷開和重連,導致資源浪費;
  2. 斷開重連: 讓連接一直在線,保證了連接的穩定性。