文档章节

《netty入门与实战》笔记-06:心跳与空闲检测

Funcy1122
 Funcy1122
发布于 2018/10/22 22:24
字数 2131
阅读 659
收藏 3

本小节,我们一起探讨最后一个话题:心跳与空闲检测

首先,我们来看一下,客户端与服务端之间的网络会存在什么问题?

1. 网络问题

下图是网络应用程序普遍会遇到的一个问题:连接假死

连接假死的现象是:在某一端(服务端或者客户端)看来,底层的 TCP 连接已经断开了,但是应用程序并没有捕获到,因此会认为这条连接仍然是存在的,从 TCP 层面来说,只有收到四次握手数据包或者一个 RST 数据包,连接的状态才表示已断开。

连接假死会带来以下两大问题

  1. 对于服务端来说,因为每条连接都会耗费 cpu 和内存资源,大量假死的连接会逐渐耗光服务器的资源,最终导致性能逐渐下降,程序奔溃。
  2. 对于客户端来说,连接假死会造成发送数据超时,影响用户体验。

通常,连接假死由以下几个原因造成的

  1. 应用程序出现线程堵塞,无法进行数据的读写。
  2. 客户端或者服务端网络相关的设备出现故障,比如网卡,机房故障。
  3. 公网丢包。公网环境相对内网而言,非常容易出现丢包,网络抖动等现象,如果在一段时间内用户接入的网络连续出现丢包现象,那么对客户端来说数据一直发送不出去,而服务端也是一直收不到客户端来的数据,连接就一直耗着。

如果我们的应用是面向用户的,那么公网丢包这个问题出现的概率是非常大的。对于内网来说,内网丢包,抖动也是会有一定的概率发生。一旦出现此类问题,客户端和服务端都会受到影响,接下来,我们分别从服务端和客户端的角度来解决连接假死的问题。

2. 服务端空闲检测

对于服务端来说,客户端的连接如果出现假死,那么服务端将无法收到客户端的数据,也就是说,如果能一直收到客户端发来的数据,那么可以说明这条连接还是活的,因此,服务端对于连接假死的应对策略就是空闲检测。

何为空闲检测?空闲检测指的是每隔一段时间,检测这段时间内是否有数据读写,简化一下,我们的服务端只需要检测一段时间内,是否收到过客户端发来的数据即可,Netty 自带的 IdleStateHandler 就可以实现这个功能。

接下来,我们写一个类继承自 IdleStateHandler,来定义检测到假死连接之后的逻辑。

IMIdleStateHandler.java

public class IMIdleStateHandler extends IdleStateHandler {

    private static final int READER_IDLE_TIME = 15;

    public IMIdleStateHandler() {
        super(READER_IDLE_TIME, 0, 0, TimeUnit.SECONDS);
    }

    @Override
    protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) {
        System.out.println(READER_IDLE_TIME + "秒内未读到数据,关闭连接");
        ctx.channel().close();
    }
}

首先,我们观察一下 IMIdleStateHandler 的构造函数,他调用父类 IdleStateHandler 的构造函数,有四个参数,其中第一个表示读空闲时间,指的是在这段时间内如果没有数据读到,就表示连接假死;第二个是写空闲时间,指的是 在这段时间如果没有写数据,就表示连接假死;第三个参数是读写空闲时间,表示在这段时间内如果没有产生数据读或者写,就表示连接假死。写空闲和读写空闲为0,表示我们不关心者两类条件;最后一个参数表示时间单位。在我们的例子中,表示的是:如果 15 秒内没有读到数据,就表示连接假死。 连接假死之后会回调 channelIdle() 方法,我们这个方法里面打印消息,并手动关闭连接。

3. 客户端定时发心跳

服务端在一段时间内没有收到客户端的数据,这个现象产生的原因可以分为以下两种:

  1. 连接假死。
  2. 非假死状态下确实没有发送数据。

我们只需要排除掉第二种可能性,那么连接自然就是假死的。要排查第二种情况,我们可以在客户端定期发送数据到服务端,通常这个数据包称为心跳数据包,接下来,我们定义一个 handler,定期发送心跳给服务端

HeartBeatTimerHandler.java

public class HeartBeatTimerHandler extends ChannelInboundHandlerAdapter {

    private static final int HEARTBEAT_INTERVAL = 5;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.executor().scheduleAtFixedRate(() -> {
            ctx.writeAndFlush(new HeartBeatRequestPacket());
        }, HEARTBEAT_INTERVAL, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);

        super.channelActive(ctx);
    }
}

ctx.executor() 返回的是当前的 channel 绑定的 NIO 线程,不理解没关系,只要记住就行,然后,NIO 线程有一个方法,scheduleAtFixedRate(),类似 jdk 的定时任务机制,可以每隔一段时间执行一个任务,而我们这边是实现了每隔 5 秒,向服务端发送一个心跳数据包,这个时间段通常要比服务端的空闲检测时间的一半要短一些,我们这里直接定义为空闲检测时间的三分之一,主要是为了排除公网偶发的秒级抖动。

实际在生产环境中,我们的发送心跳间隔时间和空闲检测时间可以略长一些,可以设置为几分钟级别,具体应用可以具体对待,没有强制的规定。

我们上面其实解决了服务端的空闲检测问题,服务端这个时候是能够在一定时间段之内关掉假死的连接,释放连接的资源了,但是对于客户端来说,我们也需要检测到假死的连接。

4. 服务端回复心跳与客户端空闲检测

客户端的空闲检测其实和服务端一样,依旧是在客户端 pipeline 的最前方插入 IMIdleStateHandler

NettyClient.java

bootstrap
        .handler(new ChannelInitializer<SocketChannel>() {
            public void initChannel(SocketChannel ch) {
                // 空闲检测
                ch.pipeline().addLast(new IMIdleStateHandler());
                ch.pipeline().addLast(new Spliter());
                // ...

然后为了排除是否是因为服务端在非假死状态下确实没有发送数据,服务端也要定期发送心跳给客户端。

而其实在前面我们已经实现了客户端向服务端定期发送心跳,服务端这边其实只要在收到心跳之后回复客户端,给客户端发送一个心跳响应包即可。如果在一段时间之内客户端没有收到服务端发来的数据,也可以判定这条连接为假死状态。

因此,服务端的 pipeline 中需要再加上如下一个 handler - HeartBeatRequestHandler, HeartBeatRequestHandler 相应的实现为

@ChannelHandler.Sharable
public class HeartBeatRequestHandler extends SimpleChannelInboundHandler<HeartBeatRequestPacket> {
    public static final HeartBeatRequestHandler INSTANCE = new HeartBeatRequestHandler();

    private HeartBeatRequestHandler() {

    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HeartBeatRequestPacket requestPacket) {
        ctx.writeAndFlush(new HeartBeatResponsePacket());
    }
}

实现非常简单,只是简单地回复一个 HeartBeatResponsePacket 数据包。客户端在检测到假死连接之后,断开连接,然后可以有一定的策略去重连,重新登录等等,这里就不展开了,留给读者自行实现。

5. 总结

  1. 我们首先讨论了连接假死相关的现象以及产生的原因。
  2. 要处理假死问题首先我们要实现客户端与服务端定期发送心跳,在这里,其实服务端只需要对客户端的定时心跳包进行回复。
  3. 客户端与服务端如果都需要检测假死,那么直接在 pipeline 的最前方插入一个自定义 IdleStateHandler,在 channelIdle() 方法里面自定义连接假死之后的逻辑。
  4. 通常空闲检测时间要比发送心跳的时间的两倍要长一些,这也是为了排除偶发的公网抖动,防止误判。

以上内容来源于掘金小册《Netty 入门与实战:仿写微信 IM 即时通讯系统》,若想获得更多,更详细的内容,请用微信扫码订阅:

本文转载自:https://juejin.im/book/5b4bc28bf265da0f60130116/section/5b4db16de51d4519601ab69f

Funcy1122

Funcy1122

粉丝 11
博文 198
码字总数 279468
作品 0
广州
后端工程师
私信 提问
Netty实现高性能、高可靠、可定制和可扩展分析

Netty如何实现高性能? 性能是设计出来的,不是测试出来的。Netty采用如下设计实现高性能: 采用异步非阻塞的I/O类库,基于Reactor模式实现,解决了传统同步阻塞I/O模式下一个服务端无法平滑...

宸明
2018/09/08
62
0
《深入探索Netty原理及源码分析》文集小结

写在2017年末尾,翻看文集的第一篇文章已经是三个月前的事了,也没想过这文集会写那么久,这么慢。。。 Netty文集中的文章主要都是我学习过程的笔记,写博客的主要目的是为了通过输出来倒逼输...

tomas家的小拨浪鼓
2017/12/30
0
0
Netty 入门与实战:仿写微信 IM 即时通讯系统

被小伙伴日常催更了一个多月的 Netty 小册终于和大伙正式见面了,为了答谢小伙伴对小册的厚爱,我将赠送15个免费阅读小册的兑换码。 有两种方式可以获取免费兑换码: 1. 在本篇文章后面留言:...

闪电侠的博客
2018/09/26
0
0
Netty 那些事儿 ——— 心跳机制

本文是Netty文集中“Netty 那些事儿”系列的文章。主要结合在开发实战中,我们遇到的一些“奇奇怪怪”的问题,以及如何正确且更好的使用Netty框架,并会对Netty中涉及的重要设计理念进行介绍...

tomas家的小拨浪鼓
2017/11/11
0
0
Netty 4.0 实现心跳检测和断线重连

一 实现心跳检测 原理:当服务端每隔一段时间就会向客户端发送心跳包,客户端收到心跳包后同样也会回一个心跳包给服务端 一般情况下,客户端与服务端在指定时间内没有任何读写请求,就会认为...

引鸩怼孑
2016/02/18
230
0

没有更多内容

加载失败,请刷新页面

加载更多

Jenkins admin 密码忘记解决

一、admin密码未更改情况 1.进入\Jenkins\secrets目录,打开initialAdminPassword文件,复制密码; find / -name initialAdminPassword [root@jenkins jenkins]# cat /var/lib/jenkins/secre......

SuShine
31分钟前
5
0
LiveData原理分析

LiveData原理分析 1 LiveData简介 大部分Android应用会从网络或SQLite数据库存取数据,并根据数据更新界面。为了避免ANR,主线程中不能存取数据。而后台线程中无法更新界面。通常的做法是让后...

tommwq
45分钟前
4
0
Java描述设计模式(20):命令模式

本文源码:GitHub·点这里 || GitEE·点这里 一、生活场景 1、场景描述 智能电脑的品牌越来越多,由此诞生了一款电脑控制的APP,万能遥控器,用户在使用遥控器的时候,可以切换为自家电视的品...

知了一笑
46分钟前
3
0
java---网络编程(上)

1.1网络编程 网络编程指的是编写运行在多个设备计算机的程序,这些计算机通过网络连接起来 java.net包中提供了两种常见的网络协议的支持: TCP:TCP是传输控制层协议的缩写,它保障了两个应用...

Firefly-
50分钟前
15
0
城市搜索插件 city-query

  今天,给大家介绍一个比较简单有用的插件city-query,大家可以从coding上面下载下来。 git clone https://gitee.com/jflsy/city-query.git   引用插件时只需要src文件下的内容就可以了...

芳缘
55分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部