最近因为项目组在优化跨服重连的问题,就简单地研究了下Netty的心跳和重连,先来简单看一下这个过程:
1)客户端连接服务端
2)在客户端的的ChannelPipeline中加入一个比较特殊的IdleStateHandler,设置一下客户端的写空闲时间,例如5s
3)当客户端的所有ChannelHandler中4s内没有write事件,则会触发userEventTriggered方法
4)我们在客户端的userEventTriggered中对应的触发事件下发送一个心跳包给服务端,检测服务端是否还存活,防止服务端已经宕机,客户端还不知道(在此,客户端可以维护userEventTriggered的次数,超过这个次数客户端主动close,否则发送心跳包)
5)同样,服务端要对心跳包做出响应,其实给客户端最好的回复就是“不回复”,这样可以减轻服务端的压力,假如有10w个空闲Idle的连接,那么服务端光发送心跳回复,则也是费事的事情,那么怎么才能告诉客户端它还活着呢,其实很简单,因为5s服务端都会收到来自客户端的心跳信息,那么如果10秒内收不到,服务端可以认为客户端挂了,可以close链路
6)加入服务端因为什么因素导致宕机的话,就会关闭所有的链路链接,所以作为客户端要做的事情就是短线重连
实现思路就是这样,当然生产情况下会更复杂一点。
IdleStateHandler
Netty4.0提供了一个类,名为IdleStateHandler,这个类可以对三种类型的心跳检测
这个类的构造参数是这样的:
前三个的参数解释如下:
1)readerIdleTime:为读超时时间(即测试端一定时间内未接受到被测试端消息)
2)writerIdleTime:为写超时时间(即测试端一定时间内向被测试端发送消息)
3)allIdleTime:所有类型的超时时间
我们在channel链中加入了IdleSateHandler,第一个参数是5,单位是秒,那么这样做的意思就是:在服务器端会每隔5秒来检查一下channelRead方法被调用的情况,如果在5秒内该链上的channelRead方法都没有被触发,就会调用userEventTriggered方法:
初步地看下IdleStateHandler源码,先看下IdleStateHandler中的channelRead方法:
请注意254行代码其实表示该方法只是进行了透传,不做任何业务逻辑处理,让channelPipe中的下一个handler处理channelRead方法,但是记录了一下这里的调用时间
我们再看看channelActive方法:
这里有个initialize的方法,这是IdleStateHandler的精髓,接着探究:
这边会触发一个Task,ReaderIdleTimeoutTask,这个task是部分源码是这样的:
341行是这样的,用当前时间减去最后一次channelRead方法调用的时间,假如这个结果是6s,说明最后一次调用channelRead已经是6s之前的事情了,你设置的是5s,那么nextDelay则为-1,说明超时了,那么354行则会触发userEventTriggered方法:
如果没有超时则不触发userEventTriggered方法
初略地看下就是这么多了,这就是IdleStateHandler的基本原理了
简而言之:
IdleStateHandler这个类会根据你设置的超时参数的类型和值,循环去检测channelRead和write方法多久没有被调用了,如果这个时间超过了你设置的值,那么就会触发对应的事件,read触发read,write触发write,all触发all
如果超时了,则会调用userEventTriggered方法,且会告诉你超时的类型
如果没有超时,则会循环定时检测,除非你将IdleStateHandler移除Pipeline
后记
—— Ariescat 记于 2019.01.30