netty的读写空闲检测-IdleStateHandler

文章介绍了Netty中的IdleStateHandler如何用于TCP长连接的心跳检测,通过定时任务管理读、写和读写空闲状态,以确保连接的稳定性和及时发现并处理无活动的连接。在服务端,这有助于清理无效连接;在客户端,能提前重新连接以减少延迟。同时,文章提到了IdleStateHandler在Dubbo中的使用,服务器端配置读写空闲检测,客户端配置读空闲检测,以及客户端的重连机制。
摘要由CSDN通过智能技术生成

前言

Netty客户端和服务端之间的Tcp长连接通过心跳包探测连通性。

1.对于服务端而言,定时剔除那些没用的连接,减轻服务端的压力,增加稳定性;

2.对于客户端而言,可以提前进行重新连接,减少请求后续连接耗时。

在Netty中,提供了IdleStateHandler类提供心跳检测,下面分析IdleStateHandler原理何在dubbo中的使用。

一.源码解析

IdleStateHandler类中初始化方法

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;
        //默认为false,检测ChannelOutboundBuffer中是否有字节变化
        initOutputChanged(ctx);

        //初始化设置的三个任务调度器
        lastReadTime = lastWriteTime = ticksInNanos();
        if (readerIdleTimeNanos > 0) {
            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);
        }
    }

在AbstractScheduledEventExecutor类中,将任务添加到延迟队列ScheduledTaskQueue中

    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
        ObjectUtil.checkNotNull(command, "command");
        ObjectUtil.checkNotNull(unit, "unit");
        if (delay < 0) {
            delay = 0;
        }
        validateScheduled0(delay, unit);

        //调度延迟任务
        return schedule(new ScheduledFutureTask<Void>(
                this,
                command,
                deadlineNanos(unit.toNanos(delay))));
    }

在NioEventLoop中获取延迟到期的任务

System.nanoTime()是当前系统时间

设定的时间:long deadlineNanos = nanoTime() + delay; 

                     nanoTime() = System.nanoTime() - START_TIME;

比较的时间:System.nanoTime() - START_TIME 


    /**
     * Poll all tasks from the task queue and run them via {@link Runnable#run()} method.
     *
     * @return {@code true} if and only if at least one task was run
     */
    protected boolean runAllTasks() {
        assert inEventLoop();
        boolean fetchedAll;
        boolean ranAtLeastOne = false;

        do {
            //从中获取延迟到期的任务
            fetchedAll = fetchFromScheduledTaskQueue();
            if (runAllTasksFrom(taskQueue)) {
                ranAtLeastOne = true;
            }
        } while (!fetchedAll); // keep on processing until we fetched all scheduled tasks.

        if (ranAtLeastOne) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
        }
        afterRunningAllTasks();
        return ranAtLeastOne;
    }
   private boolean fetchFromScheduledTaskQueue() {
        if (scheduledTaskQueue == null || scheduledTaskQueue.isEmpty()) {
            return true;
        }
        long nanoTime = AbstractScheduledEventExecutor.nanoTime();
        for (;;) {
            Runnable scheduledTask = pollScheduledTask(nanoTime);
            if (scheduledTask == null) {
                return true;
            }
            //任务队列不满则加入,满了在放回延迟队列
            if (!taskQueue.offer(scheduledTask)) {
                // No space left in the task queue add it back to the scheduledTaskQueue so we pick it up again.
                scheduledTaskQueue.add((ScheduledFutureTask<?>) scheduledTask);
                return false;
            }
        }
    }

接着从taskQueue顺序拿出任务,开始执行任务;

1.ReaderIdleTimeoutTask读空闲任务

    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;
            }
            //超过了读空闲时间,触发IdleStateEvent事件并且重新调度
            if (nextDelay <= 0) {
                // Reader is idle - set a new timeout and notify the callback.
                readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);

                boolean first = firstReaderIdleEvent;
                firstReaderIdleEvent = false;

                try {
                    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);
            }
        }
    }

上面的short delay注释解释了,确保在设置的readerIdleTimeNanos周期内有无读时间发生。

最终,触发了读空闲后会发出一个ctx.fireUserEventTriggered(evt)事件,可以在自定的handler的userEventTriggered方法中处理。

更新标识的时机:

读的过程中会置位reading为true,channelReadComplete后会置为false;

channelReadComplete会更新lastReadTime为最后一次读的时间。

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
            reading = true;
            firstReaderIdleEvent = firstAllIdleEvent = true;
        }
        ctx.fireChannelRead(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
            lastReadTime = ticksInNanos();
            reading = false;
        }
        ctx.fireChannelReadComplete();
    }

2.WriterIdleTimeoutTask写空闲任务

    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 {
                    //这个默认是false,不会触发
                    //如果为true,则ChannelOutboundBuffer中有字节的写出则不会触发写空闲,
                    //如果写出过于缓慢,则可能导致ChannelOutboundBuffer链表过长导致OOMb
                    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);
            }
        }
    }

写的逻辑和读的大致一样。

写标识更新时机:

在最终writeAndFlush将ChannelOutboundBuffer中的entry通过nio channel写出时。

    private final ChannelFutureListener writeListener = new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            lastWriteTime = ticksInNanos();
            firstWriterIdleEvent = firstAllIdleEvent = true;
        }
    };

ChannelOutboundBuffer类中的remove方法

/**
     * Will remove the current message, mark its {@link ChannelPromise} as success and return {@code true}. If no
     * flushed message exists at the time this method is called it will return {@code false} to signal that no more
     * messages are ready to be handled.
     */
    public boolean remove() {
        Entry e = flushedEntry;
        if (e == null) {
            clearNioBuffers();
            return false;
        }
        Object msg = e.msg;

        ChannelPromise promise = e.promise;
        int size = e.pendingSize;

        removeEntry(e);

        if (!e.cancelled) {
            // only release message, notify and decrement if it was not canceled before.
            ReferenceCountUtil.safeRelease(msg);
            safeSuccess(promise);
            decrementPendingOutboundBytes(size, false, true);
        }

        // recycle the entry
        e.recycle();

        return true;
    }

3.AllIdleTimeoutTask读写空闲任务

和单独的读、写大致一样。

二.IdleStateHandler在dubbo中使用

server端:

初始化IdleStateHandler,服务端心跳时长是客户端三倍,注册的是读写空闲

 处理的nettyServerHandler的userEventTriggered方法,服务端读写空闲事件,直接关闭channel连接

client端:

初始化IdleStateHandler,客户端心跳时长是服务端三分之一,注册的是读空闲

客户端重连

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值