Netty源码解析7-ChannelHandler实例之TimeoutHandler

本文深入解析了Netty中的TimeoutHandler,包括IdleStateHandler、ReadTimeoutHandler和WriteTimeoutHandler在TCP服务中的心跳保活应用。重点分析了IdleStateHandler的读超时事件,解释了其初始化、销毁过程以及核心调度任务ReaderIdleTimeoutTask的工作原理。此外,还讨论了如何自定义ReadTimeoutHandler以处理读超时事件。
摘要由CSDN通过智能技术生成

请戳GitHub原文: https://github.com/wangzhiwubigdata/God-Of-BigData

TimeoutHandler

在开发TCP服务时,一个常见的需求便是使用心跳保活客户端。而Netty自带的三个超时处理器IdleStateHandler,ReadTimeoutHandler和WriteTimeoutHandler可完美满足此需求。其中IdleStateHandler可处理读超时(客户端长时间没有发送数据给服务端)、写超时(服务端长时间没有发送数据到客户端)和读写超时(客户端与服务端长时间无数据交互)三种情况。这三种情况的枚举为:

public enum IdleState {
        READER_IDLE,    // 读超时
        WRITER_IDLE,    // 写超时
        ALL_IDLE    // 数据交互超时
    }

以IdleStateHandler的读超时事件为例进行分析,首先看类签名:

 public class IdleStateHandler extends ChannelDuplexHandler

注意到此Handler没有Sharable注解,这是因为每个连接的超时时间是特有的即每个连接有独立的状态,所以不能标注Sharable注解。继承自ChannelDuplexHandler是因为既要处理读超时又要处理写超时。
该类的一个典型构造方法如下:

    public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, 
                int allIdleTimeSeconds) {
        this(readerIdleTimeSeconds, writerIdleTimeSeconds,  
                allIdleTimeSeconds, TimeUnit.SECONDS);
    }

分别设定各个超时事件的时间阈值。以读超时事件为例,有以下相关的字段:

// 用户配置的读超时时间
    private final long readerIdleTimeNanos;
    // 判定超时的调度任务Future
    private ScheduledFuture<?> readerIdleTimeout;
    // 最近一次读取数据的时间
    private long lastReadTime;
    // 是否第一次读超时事件
    private boolean firstReaderIdleEvent = true;
    // 状态,0 - 无关, 1 - 初始化完成 2 - 已被销毁
    private byte state; 
    // 是否正在读取
    private boolean reading;

首先看初始化方法initialize():

    private void initialize(ChannelHandlerContext ctx) {
        switch (state) {
        case 1: // 初始化进行中或者已完成
        case 2: // 销毁进行中或者已完成
            return;
        }
        
        state = 1;
        lastReadTime = ticksInNanos();
        if (readerIdleTimeNanos > 0) {
            readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
                    readerIdleTimeNanos, TimeUnit.NANOSECONDS);
        }

初始化的工作较为简单,设定最近一次读取时间lastReadTime为当前系统时间,然后在用户设置的读超时时间readerIdleTimeNanos截止时,执行一个ReaderIdleTimeoutTask进行检测。其中使用的方法很简洁,如下:

     long ticksInNanos() {
        return System.nanoTime();
    }
    
    ScheduledFuture<?> schedule(ChannelHandlerContext ctx, Runnable task, 
              long delay, TimeUnit unit) {
        return ctx.executor().schedule(task, delay, unit);
    }

然后,分析销毁方法destroy():

private void destroy() {
        state = 2;  // 这里结合initialize对比理解
        if (readerIdleTimeout != null) {
            // 取消调度任务,并置null
            readerIdleTimeout.cancel(false);
            readerIdleTimeout = null;
        }
    }

可知销毁的处理也很简单,分析完初始化和销毁,再看这两个方法被调用的地方,initialize()在三个方法中被调用:

public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isActive() &&
                ctx.channel().isRegistered()) {
            initialize(ctx);
        } 
    }
    
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isActive()) {
            initialize(ctx);
        }
        super.channelRegistered(ctx);
    }
    
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        initialize(ctx);
        super.channelActive(ctx);
    }

当客户端与服务端成功建立连接后,Channel被激活,此时channelActive的初始化被调用;如果Channel被激活后,动态添加此Handler,则handlerAdded的初始化被调用;如果Channel被激活,用户主动切换Channel的执行线程Executor,则channelRegistered的初始化被调用。这一部分较难理解,请仔细体会。destroy()则有两处调用:

 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        destroy();
        super.channelInactive(ctx);
    }
    
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        destroy();
    }

即该Handler被动态删除时,handlerRemoved的销毁被执行;Channel失效时,channelInactive的销毁被执行。
分析完这些,在分析核心的调度任务ReaderIdleTimeoutTask:

private final class ReaderIdleTimeoutTask implements Runnable {
        
        private final ChannelHandlerContext ctx;
        
        ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
            this.ctx = ctx;
        }

        @Override
        protected void run() {
            if (!ctx.channel().isOpen()) {
                // Channel不再有效
                return;
            }
            
            long nextDelay = readerIdleTimeNanos;
            if (!reading) {
                // nextDelay<=0 说明在设置的超时时间内没有读取数据
                nextDelay -= ticksInNanos() - lastReadTime;
            }
            // 隐含正在读取时,nextDelay = readerIdleTimeNanos > 0

            if (nextDelay <= 0) {
                // 超时时间已到,则再次调度该任务本身
                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 {
                // 注意此处的nextDelay值,会跟随lastReadTime刷新
                readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
            }
        }
    }

这个读超时检测任务执行的过程中又递归调用了它本身进行下一次调度,请仔细品味该种使用方法。再列出channelIdle()的代码:

 protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) 
                  throws Exception {
        ctx.fireUserEventTriggered(evt);
    }

本例中,该方法将写超时事件作为用户事件传播到下一个Handler,用户需要在某个Handler中拦截该事件进行处理。该方法标记为protect说明子类通常可覆盖,ReadTimeoutHandler子类即定义了自己的处理:

@Override
    protected final void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt)
                   throws Exception {
        assert evt.state() == IdleState.READER_IDLE;
        readTimedOut(ctx);
    }

    protected void readTimedOut(ChannelHandlerContext ctx) throws Exception {
        if (!closed) {
            ctx.fireExceptionCaught(ReadTimeoutException.INSTANCE);
            ctx.close();
            closed = true;
        }
    }

可知在ReadTimeoutHandler中,如果发生读超时事件,将会关闭该Channel。当进行心跳处理时,使用IdleStateHandler较为麻烦,一个简便的方法是:直接继承ReadTimeoutHandler然后覆盖readTimedOut()进行用户所需的超时处理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xmNCsEIP-1647065341608)(https://user-gold-cdn.xitu.io/2019/2/22/16915de58b6f8285?w=300&h=390&f=png&s=14824)]

	请戳GitHub原文: https://github.com/wangzhiwubigdata/God-Of-BigData

                   关注公众号,内推,面试,资源下载,关注更多大数据技术~
                   大数据成神之路~预计更新500+篇文章,已经更新60+篇~ 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FastThreadLocal 是 Netty 中的一个优化版 ThreadLocal 实现。与 JDK 自带的 ThreadLocal 相比,FastThreadLocal 在性能上有所提升。 FastThreadLocal 的性能优势主要体现在以下几个方面: 1. 线程安全性:FastThreadLocal 使用了一种高效的方式来保证线程安全,避免了使用锁的开销,使得在高并发场景下性能更好。 2. 内存占用:FastThreadLocal 的内部数据结构更加紧凑,占用的内存更少,减少了对堆内存的占用,提高了内存的利用效率。 3. 访问速度:FastThreadLocal 在访问时,使用了直接索引的方式,避免了哈希表查找的开销,使得访问速度更快。 在 Netty 源码中,FastThreadLocal 主要被用于优化线程的局部变量存储,提高线程之间的数据隔离性和访问效率。通过使用 FastThreadLocal,Netty 在高性能的网络通信中能够更好地管理线程的局部变量,提供更高的性能和并发能力。 引用中提到的代码片段展示了 Netty 中的 InternalThreadLocalMap 的获取方式。如果当前线程是 FastThreadLocalThread 类型的线程,那么就直接调用 fastGet 方法来获取 InternalThreadLocalMap 实例;否则,调用 slowGet 方法来获取。 fastGet 方法中,会先尝试获取线程的 threadLocalMap 属性,如果不存在则创建一个新的 InternalThreadLocalMap,并设置为线程的 threadLocalMap 属性。最后返回获取到的 threadLocalMap。 slowGet 方法中,通过调用 UnpaddedInternalThreadLocalMap.slowThreadLocalMap 的 get 方法来获取 InternalThreadLocalMap 实例。如果获取到的实例为 null,则创建一个新的 InternalThreadLocalMap,并将其设置到 slowThreadLocalMap 中。最后返回获取到的 InternalThreadLocalMap。 综上所述,FastThreadLocal 是 Netty 中为了优化线程局部变量存储而设计的一种高性能的 ThreadLocal 实现。它通过减少锁的开销、优化内存占用和加快访问速度来提升性能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [FastThreadLocal源码分析](https://blog.csdn.net/lvlei19911108/article/details/118021402)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Netty 高性能之道 FastThreadLocal 源码分析(快且安全)](https://blog.csdn.net/weixin_33871366/article/details/94653953)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值