在网络通信中,当网络链路发生异常,这将会对系统的可靠性产生重大影响。那么怎么监测通信异常呢?这就是心跳机制。那么异常后怎么处理呢?这就是重连机制。
1、何为心跳
顾名思义, 所谓心跳, 即在 TCP 长连接中, 客户端和服务器之间定期发送的一种特殊的数据包, 通知对方自己还在线, 以确保 TCP 连接的有效性.
2、心跳实现方式
从技术层面看,要解决链路的可靠性问题,必须周期性的对链路进行有效性检测。目前最流行和通用的做法就是心跳检测。
心跳检测机制分为三个层面:
1) TCP层面的心跳检测,即TCP的Keep-Alive机制,它的作用域是整个TCP协议栈;
2) 协议层的心跳检测,主要存在于长连接协议中。例如SMPP协议;
3) 应用层的心跳检测,它主要由各业务产品通过约定方式定时给对方发送心跳消息实现。
不同的协议,心跳检测机制也存在差异,归纳起来主要分为两类:
1) Ping-Pong型心跳:由通信一方定时发送Ping消息,对方接收到Ping消息之后,立即返回Pong应答消息给对方,属于请求-响应型心跳;
2) Ping-Ping型心跳:不区分心跳请求和应答,由通信双方按照约定定时向对方发送心跳Ping消息,它属于双向心跳。
心跳检测策略如下:
1) 连续N次心跳检测都没有收到对方的Pong应答消息或者Ping请求消息,则认为链路已经发生逻辑失效,这被称作心跳超时;
2) 读取和发送心跳消息的时候如果发生了IO异常,说明链路已经失效,这被称为心跳失败。
无论发生心跳超时还是心跳失败,都需要关闭链路,由客户端发起重连操作,保证链路能够恢复正常。
3、服务器端实现
心跳监测机制的核心还是超时机制,所谓超时机制就是规定时间内没有收到心跳包。那么Netty中怎么实现超时检测的呢?这就是基于IdleStateHandler类。这个类能够帮助我们实现定时检测功能,我们先来看看这个类的构造函数。
在构造函数中,定义了三个变量。分别是readIdleTimeSecond,这是读超时时间。也就是这么长时间没有读到数据包,就触发超时处理机制。第二个参数是writeIdleTimeSecond,这个是写超时时间。意思就是这么长时间没有向Socket缓冲区写数据,就会触发写超时处理机制。第三个参数是allIdleTimeSeconds,这就是读写事件的超时。在规定时间内没有读到或者写数据,都将出发超时处理机制。
在使用的时候,我们只需要关注这三个参数就行。对于服务器来说,重点关注readIdleTimeSecond,指定时间内没有读到Socket缓冲区的数据,就认为异常。对于客户端来说,可以关注readIdleTimeSecond和writeIdleTimeSecond,或者可以指定allIdleTimeSeconds参数即可。
我们首先写一个服务端Demo,然后将IdleStateHandler这个处理器添加到Pipeline上面。代码如下:
我们重点关注两个地方。首先pipeline上添加IdleStateHandler,我们设置服务器读超时为5秒钟。
我们知道,默认的pipeline会将超时事件经过userEventTriggered方法向下面的handler传播(不熟悉pipeline传播机制的可以去学习一下)。因为我们添加的其他的handler是用于编解码和处理TCP半包、粘包的,没有处理这个事件,那么我们需要在自定义的ServerHandler中进行处理。
我们在这个类中定义了一个内部类ServerHandler。代码如下:
我们分别实现了读写方法(channelRead)、异常机制(exceptionCaught)、超时处理(userEventTriggered)以及客户端退出通知事件(channelInactive)。
在上面的方法中,我们重点关注超时机制处理。在进入这个userEventTriggered方法后,我们可以根据事件类型进行相应的处理。我们这里服务端重点关注读超时。这里我们将其 channel关闭。
在实际业务场景中,我们可能采用3次或者n次超时就断开客户端channel。这里我们可以加一个变量进行统计。这里不再赘述了。
4、客户端实现
下面我们来看看客户端怎么实现超时检测,以及断开重连。客户端这个类叫Client。代码如下:
和服务端一样,在设置Bootstrap的时候,在pipeline上添加IdleStateHandler处理器,用于处理超时。接下来,我们看一下重连函数doConnect。
这个重连函数其实就是一个递归函数。首先向Bootstrap得到异步事件的future接口。我们向这个接口添加监听器,假如连接失败,就向本eventloop的任务队列中添加一个定时任务(也就是一段代码)。
当然,实际业务场景下,不可能让客户端永无止境的重连。我们可以在递归的入口添加一个次数的判断,达到一定的次数就停止递归本方法。
接下来,我们看看ClientHandler类,这里我们也是写成了一个内部类。为啥这样写呢?因为学源码的。
这里,我们重点关注的是channelInactive()方法。当服务器关闭客户端channel时,会触发这个回调方法。那么我们在这个方法中调用外部类的doConnect()方法进行重连。
5、测试
我们首先启动服务端,检测到读超时,关闭客户端channel。
客户端这里,检测到客户端退出,又启动重连。
6、其他
到了这里,大概对超时机制检测和客户端断开重连有了一定的了解。在实际业务场景下,可能比这个要复杂一些。比如,我们需要写一个单独的消息当作心跳包。其中客户端发送Ping包,服务器回Pong包。
到了这里,我想起了常用的看门狗软件,定时去喂狗其实也是定时发送心跳包给看门狗。道理是一样的,我们需要学会举一反三。
好了,今天先介绍到这儿,喜欢本文的朋友请关注我的公众号。
领取专属 10元无门槛券
私享最新 技术干货