目录
一、前言
EventLoop 是 Netty 的调度中心,负责监听多种事件类型:I/O 事件、信号事件、定时事件等,而实际的业务处理逻辑则是由 ChannelPipeline 中所定义的 ChannelHandler 完成的。Netty 服务编排层的核心组件 ChannelPipeline 和 ChannelHandler 为用户提供了 I/O 事件的全部控制权。
二、ChannelPipeline 概述
Pipeline 的字面意思是管道、流水线。在 Netty 中 ChannelPipeline 起到的作用可以近似的理解为工厂流水线:原始的网络字节经过 Pipeline,被一步步加工包装,最后得到加工后的产品。
这样我们便对 ChannelPipeline 有一个初步印象:它是 Netty 的核心处理链,用以实现网络事件的动态编排和有序传播。
1. ChannelPipeline 内部结构
1.1 内部结构
- 每个 Channel 会绑定一个 ChannelPipeline, 每个 ChannelPipeline 都包含多个 ChannelHandlerContext。ChannelPipeline 中所有的 ChannelHandlerContext 通过双向链表链接在一起。
- ChannelHandlerContext 用于保存 ChannelHandler 上下文,包含生命周期的所有事件,如 connect、bind、read、flush、write、close 等。
- ChannelHandler 负责实际的数据加工和处理,每个 ChannelHandler 都对应一个 ChannelHandlerContext。当有 I/O 事件触发时, ChannelPipeline 会依次调用 ChannelHandler 对 Channel 的数据进行拦截和处理。
1.2 ChannelPipeline 双向链表的构造
ChannelPipeline 的双向链表分别维护了 HeadContext 和 TailContext 的头尾节点。我们自定义的 ChannelHandler 会插入到 Head 和 Tail 之间,这两个节点在 Netty 中已经默认实现了。
HeadContext
HeadContext 既是 Inbound 处理器,也是 Outbound 处理器。它分别实现了 ChannelInboundHandler 和 ChannelOutboundHandler。网络数据写入操作的入口就是由 HeadContext 节点完成的。HeadContext 作为 Pipeline 的头结点负责读取数据并开始传递 InBound 事件,当数据处理完成后,数据会反方向经过 Outbound 处理器,最终传递到 HeadContext,所以 HeadContext 又是处理 Outbound 事件的最后一站。此外 HeadContext 在传递事件之前,还会执行一些前置操作。
TailContext
TailContext 只实现了 ChannelInboundHandler 接口。它会在 ChannelInboundHandler 调用链路的最后一步执行,主要用于终止 Inbound 事件传播,例如释放 Message 数据资源等。TailContext 节点作为 OutBound 事件传播的第一站,仅仅是将 OutBound 事件传递给上一个节点。
1.3 入站与出站
根据网络数据的流向,ChannelHandler 分为入站 ChannelInboundHandler 和出站 ChannelOutboundHandler 两种处理器。在客户端与服务端通信的过程中,数据从客户端进入服务端的过程叫入站,反之称为出站。数据先由一系列 InboundHandler 处理后入站,然后再由相反方向的 OutboundHandler 处理完成后出站。
2. ChannelHandler 接口设计
ChannelHandler 有两个重要的子接口:ChannelInboundHandler和ChannelOutboundHandler,分别拦截入站和出站的各种 I/O 事件。
2.1 ChannelInboundHandler 的事件回调方法与触发时机
事件回调方法 | 触发时机 |
---|---|
channelRegistered | Channel 被注册到 EventLoop |
channelUnregistered | Channel 从 EventLoop 中取消注册 |
channelActive | Channel 处于就绪状态,可以被读写 |
channelInactive | Channel 处于非就绪状态Channel 可以从远端读取到数据 |
channelRead | Channel可以从远端读取到数据 |
channelReadComplete | Channel 读取数据完成 |
userEventTriggered | 用户事件触发时 |
channelWritabilityChanged | Channel 的写状态发生变化 |
2.2 ChannelOutboundHandler 的事件回调方法与触发时机
直接通过 ChannelOutboundHandler 的接口列表可以看到每种操作所对应的回调方法,如下图所示。
3. ChannelPipeline 事件传播机制
ChannelPipeline 可分为入站 ChannelInboundHandler 和出站 ChannelOutboundHandler 两种处理器,与此对应传输的事件类型可以分为Inbound 事件和 Outbound 事件。
Inbound 事件和 Outbound 事件的传播方向是不一样的,Inbound 事件的传播方向为 Head -> tail, 而 Outbound 事件的传播方向是 Tail -> Head。
4. ChannelPipeline 异常传播机制
ChannelPipeline 事件传播的实现采用了经典的责任链模式,调用链路环环相扣。如果有一个节点处理逻辑发生异常,可以通过 exceptionCaught 方法进行捕获,通过 ctx.fireExceptionCaugh 方法可以将异常按顺序传播到 Tail 节点。如果用户没有对异常进行拦截处理,最后将由 Tail 节点统一处理。
protected void onUnhandledInboundException(Throwable cause) {
try {
logger.warn(
"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.",
cause);
} finally {
ReferenceCountUtil.release(cause);
}
}
异常的统一拦截处理
在 ChannelPipeline 自定义处理器的末端添加统一的异常处理器,这也是 Netty 推荐的最佳异常处理实践。此时 ChannelPipeline 的内部结构如下图所示。
public class ExceptionHandler extends ChannelDuplexHandler {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (cause instanceof RuntimeException) {
System.out.println("Handle Business Exception Success.");
}
}
}
三、总结
关于 Netty 的服务编排,主要是通过 ChannelPipeline 管理的 ChannelHandler 链表来实现:
-
ChannelHandlerContext 是对 ChannelHandler 的封装,每个 ChannelHandler 都对应一个 ChannelHandlerContext,实际上 ChannelPipeline 维护的是与 ChannelHandlerContext 的关系。
-
I/O 事件和 Channel 数据在 ChannelHandler 链中传播, Inbound 事件和 Outbound 事件的传播方向相反,Inbound 事件的传播方向为 Head -> Tail,而 Outbound 事件传播方向是 Tail -> Head。
-
异常事件的传播顺序与 ChannelHandler 的添加顺序相同,会依次向后传播,与 Inbound 事件和 Outbound 事件无关。exceptionCaught 捕获异常, 通过在自定义处理器的末端添加统一的异常处理器优雅的处理异常。