Netty 心跳檢測(cè)
1. 前言
本節(jié),我們主要講解心跳機(jī)制 heartbeat,Netty 給我們提供了三個(gè) Handler,分別是 IdleStateHandler
、ReadTimeoutHandler
、WriteTimeoutHandler
,主要目的是檢查對(duì)方是否有效,也就是說(shuō)對(duì)方是否還在線。
2. 為什么需要心跳機(jī)制
了解 TCP: TCP 協(xié)議適用于客戶(hù)端數(shù)量相對(duì)比較少,并且通信頻繁的業(yè)務(wù)場(chǎng)景;Http 協(xié)議則適用于客戶(hù)端數(shù)量比較大的業(yè)務(wù)場(chǎng)景。因?yàn)?Http 是短連接,請(qǐng)求完成即會(huì)釋放連接資源,不再占用服務(wù)器資源,但是,TCP 則不會(huì),連接成功,則可以多次請(qǐng)求,不會(huì)釋放,除非特殊原因?qū)е逻B接斷開(kāi)。
面臨問(wèn)題: 既然長(zhǎng)連接是不會(huì)釋放連接資源,那么如果很多客戶(hù)端只是完成了連接,但是并沒(méi)有實(shí)際的業(yè)務(wù)請(qǐng)求操作,那么服務(wù)器的資源還是被占用,導(dǎo)致服務(wù)器性能下降。
解決辦法: 把那些長(zhǎng)期占用連接資源,但是并沒(méi)有實(shí)際業(yè)務(wù)操作的連接斷開(kāi)掉,等它們需要做業(yè)務(wù)操作的時(shí)候,再重連服務(wù)器。這樣可以達(dá)到即使釋放沒(méi)用的資源,提高服務(wù)器的性能。
總結(jié),心跳機(jī)制主要有以下兩個(gè)方面的作用
- 定時(shí)剔除哪些沒(méi)用的連接,減輕服務(wù)端的壓力;
- 適用于中間件(比如:RPC 框架),服務(wù)端規(guī)定時(shí)間內(nèi)沒(méi)用收到客戶(hù)端的心跳數(shù)據(jù),則可以認(rèn)為其宕機(jī),服務(wù)端剔除對(duì)于的映射關(guān)系。
3. 三個(gè)核心類(lèi)講解
為了實(shí)現(xiàn)以上需求,Netty 給我們提供了幾個(gè)特殊的 Handler 類(lèi)。
名稱(chēng) | 作用 |
---|---|
IdleStateHandler | 當(dāng)連接空閑時(shí)間(讀或?qū)懀┨L(zhǎng)時(shí),將觸發(fā) IdleStateEvent 事件,可以通過(guò) ChannelInboundHandler 中重寫(xiě) userEventTrigged 方法來(lái)處理該事件。 |
ReadTimeoutHandler | 如果在指定的時(shí)間之內(nèi)沒(méi)有發(fā)生讀事件,就會(huì)拋出這個(gè)異常,并且自動(dòng)關(guān)閉連接??梢栽?exectionCaught 方法中處理這個(gè)異常。 |
WriteTimeoutHandler | 如果在指定的時(shí)間之內(nèi)沒(méi)有發(fā)生寫(xiě)事件,拋出次異常,并且關(guān)閉連接??梢栽?exectionCaught 方法中處理這個(gè)異常。 |
IdleStateHandler 構(gòu)造函數(shù)說(shuō)明
/*
* readerIdleTimeSeconds 讀事件空閑時(shí)間,如果為0則表示禁用
* writerIdleTimeSeconds 寫(xiě)事件空閑時(shí)間,如果為0則表示禁用
* allIdleTimeSeconds 讀寫(xiě)事件空閑時(shí)間
*/
public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {
}
public IdleStateHandler(long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit) {
}
總結(jié):都是利用定時(shí)器調(diào)度任務(wù)完成。 IdleStateHandler 超時(shí)調(diào)用 handler 的 userEventTriggered 方法。ReadTimeOutHandler 超時(shí)拋出異常,調(diào)用 handler 的 exceptionCaught 方法,并且會(huì)關(guān)閉 channel。
4. 案例測(cè)試
4.1 服務(wù)端
實(shí)例:
ChannelPipeline pipeline = ch.pipeline();
//5秒鐘沒(méi)有讀事件,則斷開(kāi)連接
pipeline.addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
//5秒鐘沒(méi)有寫(xiě)事件,則斷開(kāi)連接
pipeline.addLast(new WriteTimeoutHandler(5, TimeUnit.SECONDS));
//解碼器
pipeline.addLast(new StringDecoder());
//編碼器
pipeline.addLast(new StringEncoder());
//業(yè)務(wù)Handler
pipeline.addLast(new HeartBeanHandler());
public class HeartBeanHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("channelRead>>>"+msg+">>>"+ LocalDateTime.now());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exceptionCaught>>>"+cause.getMessage());
}
}
4.2 客戶(hù)端 1
實(shí)例:延遲 1 秒鐘,每個(gè) 15 秒鐘往服務(wù)端發(fā)送一次 hello world
。
channelFuture.channel().eventLoop().scheduleWithFixedDelay(new Runnable() {
public void run() {
channelFuture.channel().writeAndFlush("hello world");
}
},1,15, TimeUnit.SECONDS);
服務(wù)端執(zhí)行結(jié)果:
channelRead>>>hello world>>>2020-07-26T15:16:08.893
exceptionCaught>>>null
客戶(hù)端執(zhí)行結(jié)果:
客戶(hù)端關(guān)閉了
Process finished with exit code 0
代碼說(shuō)明:
- 客戶(hù)端每隔 15 秒發(fā)送一次數(shù)據(jù);
- 服務(wù)端如果 5 秒之內(nèi)沒(méi)有讀寫(xiě)事件,則自動(dòng)斷開(kāi)連接;
- 從時(shí)間設(shè)置上來(lái)看,客戶(hù)端每次發(fā)送數(shù)據(jù)都是超時(shí)了,因此,連接會(huì)被斷開(kāi)。
4.3 客戶(hù)端 2
實(shí)例:延遲 1 秒鐘,每個(gè) 3 秒鐘往服務(wù)端發(fā)送一次 hello world
。
channelFuture.channel().eventLoop().scheduleWithFixedDelay(new Runnable() {
public void run() {
channelFuture.channel().writeAndFlush("hello world");
}
},1,3, TimeUnit.SECONDS);
服務(wù)端執(zhí)行結(jié)果:
channelRead>>>hello world>>>2020-07-26T15:15:10.889
channelRead>>>hello world>>>2020-07-26T15:15:13.892
channelRead>>>hello world>>>2020-07-26T15:15:16.893
channelRead>>>hello world>>>2020-07-26T15:15:19.894
代碼說(shuō)明:
- 客戶(hù)端每隔 3 秒發(fā)送一次數(shù)據(jù);
- 服務(wù)端如果 5 秒之內(nèi)沒(méi)有讀寫(xiě)事件,則自動(dòng)斷開(kāi);
- 從時(shí)間設(shè)置上來(lái)看,客戶(hù)端每次發(fā)送數(shù)據(jù)的時(shí)間都在超時(shí)時(shí)間范圍之內(nèi),因此,連接不會(huì)被斷開(kāi)。
4.4 特殊說(shuō)明
ReadTimeoutHandler 和 WriteTimeoutHandler 既可以用于客戶(hù)端,也可以用于服務(wù)端,或者兩邊同時(shí)使用。常見(jiàn)的業(yè)務(wù)場(chǎng)景如下所示:
場(chǎng)景一: 如果是服務(wù)端往客戶(hù)端推送消息(消息推送),則 WriteTimeoutHandler 用于服務(wù)端,ReadTimeoutHandler 用于客戶(hù)端;
場(chǎng)景二: 如果是客戶(hù)端主動(dòng)發(fā)起請(qǐng)求的業(yè)務(wù)(比如:IM),則 WriteTimeoutHandler 用于客戶(hù)端,ReadTimeoutHandler 用于服務(wù)端。
4.5 IdleStateHandler 的使用
實(shí)例:管道中添加 IdleStateHandler
// 空閑檢測(cè)
ch.pipeline().addLast(new IdleStateHandler(60,45,20,TimeUnit.SECONDS));
// 業(yè)務(wù)Handler
ch.pipeline().addLast(new HeartBeatHandler());
5. 小結(jié)
本節(jié)主要掌握的知識(shí)點(diǎn)
- 為什么需要做心跳檢測(cè),以及它的常見(jiàn)作用,分別是:主動(dòng)剔除無(wú)用連接減輕服務(wù)端壓力、用于中間件的心跳檢測(cè);
- Netty 提了三個(gè)核心類(lèi),分別是
IdleStateHandler
、ReadTimeoutHandler
、WriteTimeoutHandler
,它們的作用以及使用方法。