章节索引 :

Netty 心跳检测

1. 前言

本节,我们主要讲解心跳机制 heartbeat,Netty 给我们提供了三个 Handler,分别是 IdleStateHandlerReadTimeoutHandlerWriteTimeoutHandler,主要目的是检查对方是否有效,也就是说对方是否还在线。

2. 为什么需要心跳机制

了解 TCP: TCP 协议适用于客户端数量相对比较少,并且通信频繁的业务场景;Http 协议则适用于客户端数量比较大的业务场景。因为 Http 是短连接,请求完成即会释放连接资源,不再占用服务器资源,但是,TCP 则不会,连接成功,则可以多次请求,不会释放,除非特殊原因导致连接断开。

面临问题: 既然长连接是不会释放连接资源,那么如果很多客户端只是完成了连接,但是并没有实际的业务请求操作,那么服务器的资源还是被占用,导致服务器性能下降。

解决办法: 把那些长期占用连接资源,但是并没有实际业务操作的连接断开掉,等它们需要做业务操作的时候,再重连服务器。这样可以达到即使释放没用的资源,提高服务器的性能。

总结,心跳机制主要有以下两个方面的作用

  1. 定时剔除哪些没用的连接,减轻服务端的压力;
  2. 适用于中间件(比如:RPC 框架),服务端规定时间内没用收到客户端的心跳数据,则可以认为其宕机,服务端剔除对于的映射关系。

图片描述

3. 三个核心类讲解

为了实现以上需求,Netty 给我们提供了几个特殊的 Handler 类。

名称 作用
IdleStateHandler 当连接空闲时间(读或写)太长时,将触发 IdleStateEvent 事件,可以通过 ChannelInboundHandler 中重写 userEventTrigged 方法来处理该事件。
ReadTimeoutHandler 如果在指定的时间之内没有发生读事件,就会抛出这个异常,并且自动关闭连接。可以在 exectionCaught 方法中处理这个异常。
WriteTimeoutHandler 如果在指定的时间之内没有发生写事件,抛出次异常,并且关闭连接。可以在 exectionCaught 方法中处理这个异常。

IdleStateHandler 构造函数说明

/*
* readerIdleTimeSeconds 读事件空闲时间,如果为0则表示禁用
* writerIdleTimeSeconds 写事件空闲时间,如果为0则表示禁用
* allIdleTimeSeconds 读写事件空闲时间
*/
public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {
    
}

public IdleStateHandler(long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit) {
    
}

总结:都是利用定时器调度任务完成。 IdleStateHandler 超时调用 handler 的 userEventTriggered 方法。ReadTimeOutHandler 超时抛出异常,调用 handler 的 exceptionCaught 方法,并且会关闭 channel。

4. 案例测试

4.1 服务端

实例:

ChannelPipeline pipeline = ch.pipeline();

//5秒钟没有读事件,则断开连接
pipeline.addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
//5秒钟没有写事件,则断开连接
pipeline.addLast(new WriteTimeoutHandler(5, TimeUnit.SECONDS));
//解码器
pipeline.addLast(new StringDecoder());
//编码器
pipeline.addLast(new StringEncoder());
//业务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 客户端 1

实例:延迟 1 秒钟,每个 15 秒钟往服务端发送一次 hello world

channelFuture.channel().eventLoop().scheduleWithFixedDelay(new Runnable() {
    public void run() {
        channelFuture.channel().writeAndFlush("hello world");
    }    
},1,15, TimeUnit.SECONDS);

服务端执行结果:

channelRead>>>hello world>>>2020-07-26T15:16:08.893
exceptionCaught>>>null

客户端执行结果:

客户端关闭了
Process finished with exit code 0

代码说明:

  1. 客户端每隔 15 秒发送一次数据;
  2. 服务端如果 5 秒之内没有读写事件,则自动断开连接;
  3. 从时间设置上来看,客户端每次发送数据都是超时了,因此,连接会被断开。

4.3 客户端 2

实例:延迟 1 秒钟,每个 3 秒钟往服务端发送一次 hello world

channelFuture.channel().eventLoop().scheduleWithFixedDelay(new Runnable() {
    public void run() {
        channelFuture.channel().writeAndFlush("hello world");
    }    
},1,3, TimeUnit.SECONDS);

服务端执行结果:

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

代码说明:

  1. 客户端每隔 3 秒发送一次数据;
  2. 服务端如果 5 秒之内没有读写事件,则自动断开;
  3. 从时间设置上来看,客户端每次发送数据的时间都在超时时间范围之内,因此,连接不会被断开。

4.4 特殊说明

ReadTimeoutHandler 和 WriteTimeoutHandler 既可以用于客户端,也可以用于服务端,或者两边同时使用。常见的业务场景如下所示:

场景一: 如果是服务端往客户端推送消息(消息推送),则 WriteTimeoutHandler 用于服务端,ReadTimeoutHandler 用于客户端;
场景二: 如果是客户端主动发起请求的业务(比如:IM),则 WriteTimeoutHandler 用于客户端,ReadTimeoutHandler 用于服务端。

4.5 IdleStateHandler 的使用

实例:管道中添加 IdleStateHandler

// 空闲检测
ch.pipeline().addLast(new IdleStateHandler(60,45,20,TimeUnit.SECONDS));
// 业务Handler
ch.pipeline().addLast(new HeartBeatHandler());

5. 小结

本节主要掌握的知识点

  1. 为什么需要做心跳检测,以及它的常见作用,分别是:主动剔除无用连接减轻服务端压力、用于中间件的心跳检测;
  2. Netty 提了三个核心类,分别是 IdleStateHandlerReadTimeoutHandlerWriteTimeoutHandler,它们的作用以及使用方法。