Netty 心跳服务源码剖析

23 篇文章 2 订阅
心跳检测 Hanlder说明
IdleStateHandler当连接的空闲时间(读或者写) 太长时, 将会触发一个 IdleStateEvent 事件。然后你可以通过你的 ChannelInboundHandler 中重写 userEventTrigged 方法来处理该事件
ReadTimeoutHandler如果在指定的事件没有发生读事件,就会抛出这个异常,并自动关闭这个连接。你可以在 exceptionCaught 方法中处理这个异常
WriteTimeoutHandler当一个写操作不能再一定时间内完成时,就会抛出这个异常,并自动关闭这个连接。你可以在 exceptionCaught 方法中处理这个异常

IdleStateHandler: 

属性说明
private final boolean observeOutput;是否考虑出站时较慢的情况,默认是false
private final long readerIdleTimeNanos;读事件空闲时间, 0 则禁用事件
private final long writerIdleTimeNanos;写时间空闲时间,0则禁用事件
private final long allIdleTimeNanos;读或写空闲时间,0则禁用事件

handlerAdded 方法:

当该 handler 被添加到 pipeline 中时,则调用 initialize 方法

    private void initialize(ChannelHandlerContext ctx) {
        // Avoid the case where destroy() is called before scheduling timeouts.
        // See: https://github.com/netty/netty/issues/143
        switch (state) {
        case 1:
        case 2:
            return;
        default:
             break;
        }
        
        // 将state 状态设置为1,防止重复初始化。
        state = 1;
        // 初始化 "监控出站数据属性"
        initOutputChanged(ctx);

        lastReadTime = lastWriteTime = ticksInNanos();
        // 只要给定的参数大于0,就会创建一个定时任务,每个事件都会创建
        if (readerIdleTimeNanos > 0) {
            // 这里的 schedule 方法会调用 eventLoop 的 shedule 方法,将定时任务添加进队列中
            readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
                    readerIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
        if (writerIdleTimeNanos > 0) {
            writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
                    writerIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
        if (allIdleTimeNanos > 0) {
            allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
                    allIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
    }
    
    // 返回纳秒时间
    long ticksInNanos() {
        return System.nanoTime();
    }

ReaderIdleTimeoutTask、WriterIdleTimeoutTask、AllIdleTimeoutTask 分别对应读、写、读或写 事件。共有一个父类(AbstractIdleTask)。 这个父类提供了一个模板方法

    private abstract static class AbstractIdleTask implements Runnable {

        private final ChannelHandlerContext ctx;

        AbstractIdleTask(ChannelHandlerContext ctx) {
            this.ctx = ctx;
        }
        
        // 当通道管理了,就不执行任务,反之则执行子类的 run 方法
        @Override
        public void run() {
            if (!ctx.channel().isOpen()) {
                return;
            }

            run(ctx);
        }

        protected abstract void run(ChannelHandlerContext ctx);
    }

读事件的 run 方法(即 ReaderIdleTimeoutTask 的 run 方法) 分析:

private final class ReaderIdleTimeoutTask extends AbstractIdleTask {

        ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
            super(ctx);
        }

        @Override
        protected void run(ChannelHandlerContext ctx) {
            // 得到用户设置的超时时间
            long nextDelay = readerIdleTimeNanos;
            if (!reading) {
                nextDelay -= ticksInNanos() - lastReadTime;
            }
            
            // 如果读取操作结束了(执行了 channelReadComplete 方法设置), 就用当前时间减去给定时间和最后一次读,如果小于0,就触发事件。反之,继续放入队列。间隔时间是新的计算事件
            if (nextDelay <= 0) {
                // 首先将任务再次放到队列,事件是刚开始设置的事件,返回一个promise对象,用于做取消操作
                readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);
                // 设置first 属性为 false,表示下一次读取不再是第一次了,这个属性在 channelRead 方法会被改为 true
                boolean first = firstReaderIdleEvent;
                firstReaderIdleEvent = false;

                try {
                    // 创建一个 IdleStateEvent 类型的写事件对象,将此对象传递给用户的 UserEventTriggered 方法。完成触发事件的操作
                    IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
                    channelIdle(ctx, event);
                } catch (Throwable t) {
                    ctx.fireExceptionCaught(t);
                }
            } else {
                // Read occurred before the timeout - set a new timeout with shorter delay.
                readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
            }
        }
    }

每次读取操作都会记录一个时间,定时任务时间到了,会计算当前时间和最后一次读的时间的间隔,如果间隔超过了设置的时间,就触发 UserEventTriggered 方法。

写事件的 run 方法(即 WriterIdleTimeoutTask 的 run 方法)分析:

    private final class WriterIdleTimeoutTask extends AbstractIdleTask {

        WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
            super(ctx);
        }

        @Override
        protected void run(ChannelHandlerContext ctx) {

            long lastWriteTime = IdleStateHandler.this.lastWriteTime;
            long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
            if (nextDelay <= 0) {
                // Writer is idle - set a new timeout and notify the callback.
                writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);

                boolean first = firstWriterIdleEvent;
                firstWriterIdleEvent = false;

                try {
                    // 判断出站是否有过于缓慢的数据
                    if (hasOutputChanged(ctx, first)) {
                        return;
                    }

                    IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
                    channelIdle(ctx, event);
                } catch (Throwable t) {
                    ctx.fireExceptionCaught(t);
                }
            } else {
                // Write occurred before the timeout - set a new timeout with shorter delay.
                writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
            }
        }
    }

所有事件的 run 方法(即 AllIdleTimeoutTask 的 run 方法) 分析:

    private final class AllIdleTimeoutTask extends AbstractIdleTask {

        AllIdleTimeoutTask(ChannelHandlerContext ctx) {
            super(ctx);
        }

        @Override
        protected void run(ChannelHandlerContext ctx) {

            long nextDelay = allIdleTimeNanos;
            if (!reading) {
                // 计算是取读写事件中的最大值
                nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime);
            }
            if (nextDelay <= 0) {
                // Both reader and writer are idle - set a new timeout and
                // notify the callback.
                allIdleTimeout = schedule(ctx, this, allIdleTimeNanos, TimeUnit.NANOSECONDS);

                boolean first = firstAllIdleEvent;
                firstAllIdleEvent = false;

                try {
                    if (hasOutputChanged(ctx, first)) {
                        return;
                    }

                    IdleStateEvent event = newIdleStateEvent(IdleState.ALL_IDLE, first);
                    channelIdle(ctx, event);
                } catch (Throwable t) {
                    ctx.fireExceptionCaught(t);
                }
            } else {
                // Either read or write occurred before the timeout - set a new
                // timeout with shorter delay.
                allIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
            }
        }
    }
}

小结:

  • IdleStateHandler 可以实现心跳功能,当服务器和客户端没有任何读写交互时,并超过了给定的时间,则会触发用户 handler 的 userEventTriggered 方法。用户可以在这个方法中尝试向对方发送信息,如果发送失败,则关闭连接
  • IdleStateHandler 的实现基于 EventLoop 的定时任务,每次读写都会记录一个值,在定时任务运行的时候,通过计算当前时间和设置时间和上次事件发生时间的结果,来判断是否空闲
  • 内部有 3 个定时任务, 分别对应读事件、写事件、读写事件。通常用户监听读写事件就足够了
  • 同时, IdleStateHandler 内部也考虑了一些极端情况: 客户端接收缓慢,一次接收数据的速度超过了设置的空闲时间。Netty 通过构造方法中的 observeOutput 属性来决定是否对出站缓冲区的情况进行判断
  • 如果出站缓慢,Netty 不认为这是空闲,也就不触发空闲事件。但第一次无论如何也是要触发的。因为第一次无法判断是出站缓慢还是空闲。当然,出站缓慢的话,可能造成 OOM, OOM 比空闲的问题更大
  • 当应用出现内存溢出,OOM之类,并且写空闲极少发生(使用了 observeOutput 为 true), 那么就需要注意是不是数据出站速度过慢
  • ReadTimeoutHandler, 它继承自 IdleStateHandler,当触发读空闲事件的时候,就触发 ctx.fireExceptionCaught 方法,并传入一个 ReadTimeoutException, 然后关闭 socket
  • WriteTimeoutHandler 的实现不是基于 IdleStateHandler 的,他的原理是,当调用 write 方法的时候,会创建一个定时任务,任务内容是过呢据传入的 promise 的完成情况来判断是否超出了写的时间。当定时任务根据指定时间开始运行,发现 promise 的 isDone 方法返回 false,表明还没有写完,说明超时了,则抛出异常。当 write 方法完成后,会打断定时任务
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值