Netty基础招式——ChannelHandler的最佳实践

前面我们提到了Netty中的两种事件类型,Inbound事件和Outbound事件,分别对应InboundHandler和OutbountHandler进行处理。

当我们使用Netty进行开发的时候,必须了解Inbound事件和Outbound事件在ChannelPipeline中如何进行“事件传播”,注册InboundHandler和OutboundHandler的顺序有什么影响。

话不多说,我们先来一个demo直观地感受一下。

自定义一个ChannelInboundHandler

Netty基础招式——ChannelHandler的最佳实践

自定义一个ChannelOutboundHandler

Netty基础招式——ChannelHandler的最佳实践

简单组装一下EchoPipelineServer,特别注意一下 6个handler 的注册顺序。

Netty基础招式——ChannelHandler的最佳实践

然后我们通过命令行简单访问一下这个Netty Server

curl localhost:8081

可以看到控制台的如下输出

Netty基础招式——ChannelHandler的最佳实践

这样就清楚了事件传播顺序:

- 对于Inbound事件,InboundHandler的处理顺序是和注册顺序一致

- 对于Outbound事件,OutboundHandler的处理顺序和注册顺序相反

结合上一节说的HeadContext和TailContext,我们画个图来更直观地看一下这个ChannelPipeline中的handler构建顺序是怎样的。

Netty基础招式——ChannelHandler的最佳实践

在上面的ChannelInitializer中,我们按需添加了3个InboundHandler和3个OutboundHandler。所以,在头节点HeadContext和TailContext之间,有序构成了双向链表。

而InboundHandler3中,通过调用 ctx.channel.writeAndFlush( msg ) 方法,将消息从TailContext开始,依据OutboundHandler的路径向HeadContext方向传播出去。具体可以看下DefaultChannelPipeline类中的实现

Netty基础招式——ChannelHandler的最佳实践

虽然这里是双向链表,但是无论是Inbound事件还是Outbound事件,在按序访问链表节点时,会根据事件类型进行过滤。

3. ChannelHandler的异常传播机制


我们已经了解了ChannelPipeline的链式传递规则,如果双向链表中任意一个handler抛出了异常,那么应该怎么处理呢?

3.1 InboundHandler的异常处理

我们修改下示例中的TestInboudHandler进行模拟。

  • channelRead方法中抛出异常

  • 重写exceptionCaught方法,打印当前节点捕获异常情况

Netty基础招式——ChannelHandler的最佳实践

得到输出如下

Netty基础招式——ChannelHandler的最佳实践

可以看到,虽然在InboundHander1中抛出了异常,但是仍然会被3个InboundHandler都捕获一次,并按序向tail节点方向传递,然后抛出异常。

我们也看到了,Netty给出了会警告,在最后的节点没有进行异常处理。

An exceptionCaught() event was fired, and it reached at the tail of the pipeline.

It usually means the last handler in the pipeline did not handle the exception.

3.2 OutboundHandler的异常处理

OutboundHandler也是这么操作吗?

我们来做个实验。

  • 在write操作中抛出异常

  • 重写下exceptionCaught方法(这个方法在OutboundHandler中被标记为废弃)

重写组装下channelPipeline,第二个OutboundHandler中抛出异常

Netty基础招式——ChannelHandler的最佳实践

结果得到的输出如下

Netty基础招式——ChannelHandler的最佳实践

咦?异常被吃掉了!!

不仅没有走进exceptionCaught方法,也没有其他异常抛出。

只是对后续handler的write方法不再执行,而flush方法还是都执行了一遍。

我们从源码找找原因吧。跟一下断点,马上就找到了原因:

Netty基础招式——ChannelHandler的最佳实践

AbstractChannelHandlerContext中,对OutboundHandler的write方法做了异常捕获,然后对ChannelPromise进行了通知。

后续源码就不展开了,有兴趣的同学自己打断点跟一下,比较清楚。

那么问题来了,怎么在OutboundHandler中捕获异常呢?很明显就是直接添加ChannelPromise的回调。

上代码:

Netty基础招式——ChannelHandler的最佳实践

在前面提到的ExceptionHandler中,复写write方法,然后注册一个ChannelPromise的Listener就行了。

当然,这个ExceptionHandler同样要注册到ChannelPipeline。

千万注意!!这里ExceptionHandler同样是添加到ChannelPipeline的tail方向的最后,而不是添加在head方向。

无论是inboundHandler或者是outboundHandler的异常,都是按序向tail方向传递的。

异常就这样抓到了。

Netty基础招式——ChannelHandler的最佳实践

4. ChannelHandler的最佳实践


其实前面已经对ChannelHandler的常用机制做了介绍,这里简单再介绍下两个最佳实践。

4.1 不在ChannelHandler中做耗时处理

这一点其实在前一篇《 深入Netty逻辑架构,从Reactor线程模型开始》已经提到过,这里作为自定义ChannelHandler的最佳实践再强调一下,不在ChannelHandler中做耗时处理。

这里包括两点。

一是不在I/O线程中直接处理耗时操作。

二是也不把耗时操作放进EventLoop的任务队列中。

由于Netty4的无锁串行化设计,一旦任何耗时操作阻塞了某个EventLoop,那么这个EventLoop上的各个channel都会被阻塞。更详细内容可以参考上一篇《 深入Netty逻辑架构,从Reactor线程模型开始》。

所以,我们对于耗时操作,我们要放在自己的业务线程池中进行处理,如果需要发送response,需要提交任务到EventLoop的任务队列中执行。

给个简单的demo。

Netty基础招式——ChannelHandler的最佳实践

4.2 统一的异常处理

在本文的第三节中,讲解了ChannelHandler的异常传播机制。

对于InboundHandler来说,如果你有跟handler特定相关的异常,可以直接在handler里进行exceptionCaught。如果是一些通用的异常,可以自定义ExceptionHandler注册到ChannelPipeline的末尾进行统一拦截。

对于OutboudHandler来说,就是通过自定义ExceptionHandler,重写对应方法,并注册ChannelPromise的Listener。同样的,ExceptionHandler注册到ChannelPipeline的末尾进行统一拦截。

所以,总结下如何添加一个“统一”的异常拦截器呢?

  • 自定义ExceptionHandler继承ChannelDuplexHandler,并注册到 tail节点前(ChannelPipeline的最后一个节点)

  • 对于Inbound事件,我们需要在exceptionCaught()进行处理

  • 对于Outbound事件,我们需要不同的ChannelFutureListener

异常拦截器的注册位置应该在tail方向的最后一个Handler。

Netty基础招式——ChannelHandler的最佳实践

注意,统一异常处理除了更优雅处理通用异常外,也是排查故障的好帮手。比如有时候对于编解码异常,可以在统一处理异常处捕获,快速定位问题。

5.小结


来简单回顾下吧。

本文介绍了什么是ChannelHandler和ChannelPipeline。能厘清InboundChannelHandler、OutboundChannelHandler、ChannelHandlerContext是什么吗?

然后对ChannelHandler的事件传播机制、异常处理机制做了详细介绍。

最后说明了日常开发中ChannelHandler的最佳实践。
希望对大家有所帮助。

作者:阿丸

总结

虽然面试套路众多,但对于技术面试来说,主要还是考察一个人的技术能力和沟通能力。不同类型的面试官根据自身的理解问的问题也不尽相同,没有规律可循。

上面提到的关于这些JAVA基础、三大框架、项目经验、并发编程、JVM及调优、网络、设计模式、spring+mybatis源码解读、Mysql调优、分布式监控、消息队列、分布式存储等等面试题笔记及资料

有些面试官喜欢问自己擅长的问题,比如在实际编程中遇到的或者他自己一直在琢磨的这方面的问题,还有些面试官,尤其是大厂的比如 BAT 的面试官喜欢问面试者认为自己擅长的,然后通过提问的方式深挖细节,刨根到底。

加入社区:https://bbs.youkuaiyun.com/forums/4304bb5a486d4c3ab8389e65ecb71ac0
g-gPvizBK7-1725498862094)]

[外链图片转存中…(img-HPB8NMtf-1725498862094)]

上面提到的关于这些JAVA基础、三大框架、项目经验、并发编程、JVM及调优、网络、设计模式、spring+mybatis源码解读、Mysql调优、分布式监控、消息队列、分布式存储等等面试题笔记及资料

有些面试官喜欢问自己擅长的问题,比如在实际编程中遇到的或者他自己一直在琢磨的这方面的问题,还有些面试官,尤其是大厂的比如 BAT 的面试官喜欢问面试者认为自己擅长的,然后通过提问的方式深挖细节,刨根到底。

加入社区:https://bbs.youkuaiyun.com/forums/4304bb5a486d4c3ab8389e65ecb71ac0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值