长连接的心跳保持设计

前言

TCP有两种连接场景,长连接和短连接,网络上的通信没有真实的连接,只是在通信双方保持着连接状态,通过状态的变更来表达连接的保持和释放过程;那什么是心跳保持,长连接状态下客户端和服务端为了知道对方的状态,需要定时的进行数据传输来告诉对方自己还活着,这就是心跳,本文主要讲解基于Netty的心跳保持设计。

TCP连接

长连接:

每次通信后,客户端和服务端都保持连接状态,再次通信时无需新创建连接,好处就是一次连接可以进行多次数据通信,减小创建连接的开销,缺点就是需要维持连接状态,也就是连接保活。

短连接:

每次请求都是三次握手建立连接,请求结束之后四次挥手,释放连接,优点就是简单易用,缺点就是每次通信都需要新创建连接,相比长连接开销更大。

心跳实现方式

由于长连接下的场景,不是每时每刻都存在数据通信,那么如何在数据通信时最大可能的保证连接的可用性,就需要设计一种对于连接状态监控重连的手段,也就是连接保活
image.png

那么实现以上有两种方式,本文主要讨论下应用层面的实现

  • 协议层面,即TCP的keepAlive机制,依赖于操作系统设置,实现偏底层,需要深刻的理解TCP。
  • 应用层面,由应用本身业务逻辑实现,实现更灵活,有更好的可控性。

应用层面的心跳实现

应用层面的实现包含两部分,客户端和服务端

基本思路

  • 客户端在和服务端成功建立连接后本地存储一个Channel集合
  • 启动一个定时任务,定时发送心跳报文到服务端
  • 接收到服务端响应报文之后更新本地Channel的时间
  • 另外一个定时任务判断Channel的时间是否超过阈值,超过则重连
  • 服务端在收到客户端的心跳请求之后更新Channel的时间
  • 服务端定时任务判断如果Channel的时间是否超过阈值,超过则断开连接

这样可以实现心跳探测,但是有个缺点就是发送心跳的定时任务周期性的发送心跳包,存在大量的无用请求,同时也会增加服务端的压力,那么是否存在一种机制客户端在超过一定时间没有数据请求的情况下发送心跳包来探测下服务端是否可用,这样就减少大部分的无用请求,好在Netty中通过IdleStateHandler类来满足这样的需求
image.pngimage.png
我们一般关注的为readerIdleTime,writerIdleTime,allIdleTime,这三个参数
readerIdleTime:当超过设置时间没有调用channelRead()方法则触发userEventTriggered()方法
writerIdleTime:当超过设置时间没有调用write()方法则触发userEventTriggered()方法
allIdleTime:是readerIdleTime,writerIdleTime合集
IdleStateHandler的channelActive()方法在socket通道建立时被触发,从而根据构造参数初始化对应的定时任务,判断如果对应的方法执行最后一次时间和当前时间比较,如果超过设置的阈值则触发userEventTriggered()方法
image.png
image.png

客户端实现

  • NettyClient启动时将IdleStateHandler加入到pipeline管道中 image.png
  • 自定义类实现ChannelInboundHandlerAdapter,并且重写userEventTriggered()方法 image.png
  • 收到服务端的响应之后更新Channel的时间 image.png
  • 判断如果时间间隔超过阈值则重连 image.png以上就完成了客户端的重连实现,这里边有一个地方需要注意的是关于重连的任务不可以放在第二步的userEventTriggered()中实现,因为当服务端down机之后会触发IdleStateHandler的channelInactive()方法,该方法会取消所有定时任务,就导致重连任务不能够被执行,所以我们需要单独启一个定时任务来处理超时重连逻辑。 image.pngimage.png

服务端实现

  • 接收到客户端的连接请求之后在本地存储一个Channel集合
  • 每次接收到心跳信息之后更新本地Channel的时间 image.png
  • 启动一个定时任务,判断如果时间差超过设置的阈值则关闭连接 image.png以上的实现知识基本的连接保持,服务端的心跳可以根据业务场景来调整,比如引入IdleStateHandler,配置readerIdleTime参数来监听如果超一定阈值则触发超时判断,这样做相比定时任务的执行更节省资源,这里再提到一个问题就是关于channel的写回调判断,这里的isSuccess()方法不能用来判断channel是否可用,isSuccess()方法返回true仅代表消息写到TCP 缓冲区成功了而已,(图片摘自网络),所以我们还是根据判断channel的最后更新时间来主动关闭连接。 image.png

总结

以上就是基于Netty的长连接心跳保持的基本实现,还有很多细节需要完善,比如连接重试,定时任务的时间间隔判断,服务端对心跳报文和业务报文的区分等等,都需要随着业务规模和场景的变化来不断的完善,同时对于TCP的深刻理解也至关重要。



猜你喜欢

转载自juejin.im/post/5c6cb3336fb9a049f154c5c7
今日推荐