前面分析了Reactor模式,下面来详细介绍Netty通过哪些类组件、怎样的类之间关系来实现Reactor模式的。
下面为netty中涉及到的类,以及类与类之间的关系,下面会将各个类角色和Reactor模式中结合起来理解。
图-netty-class-channel
1、Channel
Channel实现类中会包含NIO Channel,这个才是Reactor模式中的句柄,将感兴趣的事件注册到selector中进行监听
其实Channel是Netty的抽象向应用程序屏蔽了具体实现,它是一个资源的容器,包含了所有一个连接涉及到的所有资源的引用,如封装java NIO Channel、ChannelPipeline、Boss、NioWorkerPool等。另外它还提供了向内部NIO Channel写响应数据的接口write、连接/绑定到某个地址的connect/bind接口等,个人感觉虽然对Channel本身来说,因为它封装了NIO Channel,因而这些接口定义在这里是合理的,但是如果考虑到Netty的架构,它的Channel只是一个资源容器,有这个Channel实例就可以得到和它相关的基本所有资源,因而这种write、connect、bind动作不应该再由它负责,而是应该由其他类来负责,比如在Netty4中就在ChannelHandlerContext添加了write方法,虽然netty4并没有删除Channel中的write接口。
2、 ChannelPipeline
用于管理ChannelHandler的管道,每个Channel一个ChannelPipeline实例,可以运行过程中动态的向这个管道中添加、删除ChannelHandler(由于实现的限制,在最末端的ChannelHandler向后添加或删除ChannelHandler不一定在当前执行流程中起效)。ChannelPipeline内部维护一个ChannelHandler的双向链表,它以Upstream(Inbound)方向为正向,Downstream(Outbound)方向为方向。ChannelPipeline采用Intercepting Filter模式实现,这个模式的实现在后一节中还是详细介绍。
3、ChannelHandler
Reactor模式中的Event Handler, 其中ChannelHandler有两个子接口 ChannelUpstreamHandler ChannelDownstreamHandler
ChannelUpstreamHandler 从Socket进入Netty内部向用户应用程序做数据处理,一般为新消息接受、Channel状态被动改变(已经绑定成功、已经连接成功、channel被关闭) Netty4叫做 ChannelInboundHandler
ChannelDownstreamHandler 从用户程序流经Netty内部,最终向Sockt写入数据,一般为Channel状态主动改变(将要去绑定某个端口、去连接服务端)、消息写入 Netty4叫做ChannelOutboundHandler
4、ChannelEvent
Reactor模式是基于事件编程的,可在Channels帮助类产生,并将事件通过ChannelPipeline流经所有业务逻辑处理Handler, Channel事件的抽象,例如消息事件、异常事件、状态事件都是基于此接口扩展的。
ChannelStateEvent: 代表Channel状态发生变化,如果Channel 的parent channel不为空则还会传递到parent channel的ChannelPipeline中,如: BOUND OPEN CLOSE CONNECTED 这种事件会在各种不同的Channel实现 和ChannelSink中产生。
MessageEvent表示从Socket中读取数据完成、需要向Socket写数据或ChannelHandler对当前Message解析(如Decoder、Encoder)后触发的事件,它由NioWorker、需要对Message做进一步处理的ChannelHandler产生;
WriteCompletionEvent表示写完成而触发的事件,它由NioWorker产生;ExceptionEvent表示在处理过程中出现的Exception,它可以发生在各个构件中,如Channel、ChannelSink、NioWorker、ChannelHandler中;
IdleStateEvent由IdleStateHandler触发,这也是一个ChannelEvent可以无缝扩展的例子。注:在Netty4后,已经没有ChannelEvent类,所有不同事件都用对应方法表达,这也意味这ChannelEvent不可扩展,Netty4采用在ChannelInboundHandler中加入userEventTriggered()方法来实现这种扩展
5、NioSelector
Netty3 使用NioSelector来存放Selector (Synchronous Event Demultiplexer)监听注册的感兴趣句柄的事件,每个新产生的NIO Channel都向这个Selector注册自己以让这个Selector监听这个NIO Channel中发生的事件,当事件发生时,调用帮助类Channels中的方法生成ChannelEvent实例,将该事件发送到这个Netty Channel对应的ChannelPipeline中,而交给各级ChannelHandler处理。其中在向Selector注册NIO Channel时,Netty Channel实例以Attachment的形式传入,该Netty Channel在其内部的NIO Channel事件发生时,会以Attachment的形式存在于SelectionKey中,因而每个事件可以直接从这个Attachment中获取相关链的Netty Channel,并从Netty Channel中获取与之相关联的ChannelPipeline,这个实现和Doug Lea的Scalable IO In Java一模一样。另外Netty3还采用了Scalable IO In Java中相同的Main Reactor和Sub Reactor设计,其中NioSelector的两个实现:Boss即为Main Reactor,NioWorker为Sub Reactor。Boss用来处理新连接加入的事件,NioWorker用来处理各个连接对Socket的读写事件,其中Boss通过NioWorkerPool获取NioWorker实例,Netty3模式使用RoundRobin方式放回NioWorker实例。更形象一点的,可以通过Scalable IO In Java的这张图表达:
图-netty-multi-reactor
若与Ractor模式对应,NioSelector中包含了Synchronous Event Demultiplexer,而ChannelPipeline中管理着所有EventHandler,因而NioSelector和ChannelPipeline共同构成了Initiation Dispatcher。
5、ChannelSink
在ChannelHandler处理完成所有逻辑需要向客户端写响应数据时,一般会调用Netty Channel中的write方法,然而在这个write方法实现中,它不是直接向其内部的Socket写数据,而是交给Channels帮助类,内部创建DownstreamMessageEvent,反向从ChannelPipeline的管道中流过去,直到第一个ChannelHandler处理完毕,最后交给ChannelSink处理,以避免阻塞写而影响程序的吞吐量。ChannelSink将这个MessageEvent提交给Netty Channel中的writeBufferQueue,最后NioWorker会等到这个NIO Channel已经可以处理写事件时无阻塞的向这个NIO Channel写数据。这就是上图的send是从SubReactor直接出发的原因。
如果说Reactor模式是Netty3的骨架,那么Intercepting Filter模式则是Netty的中枢。Reactor模式主要应用在Netty3的内部实现,它是Netty3具有良好性能的基础,而Intercepting Filter模式则是ChannelHandler组合实现一个应用程序逻辑的基础,只有很好的理解了这个模式才能使用好Netty,甚至能得心应手。
在上文有提到Netty3的ChannelPipeline是ChannelHandler的容器,用于存储与管理ChannelHandler,同时它在Netty3中也起到桥梁的作用,即它是连接Netty3内部到所有ChannelHandler的桥梁。作为ChannelPipeline的实现者DefaultChannelPipeline,它使用一个ChannelHandlerContext的双向链表来存储,以DefaultChannelPipelineContext作为节点:
DefaultChannelPipeline实现了ChannelPipeline接口,其类中包含以下属性
属性:
Channel channel;
ChannelSink sink;
DefaultChannelHandlerContext head;
DefaultChannelHandlerContext tail;
Map name2ctx = new HashMap(4);
其中:ChannelHandlerContext保存了Netty与Handler相关上下文信息,其中DefaultChannelHandlerContext中除了包含ChannelHandler以外还有包含 next和prev两个指针指向前一个和后一个DefaultChannelHandlerContext,从而形成一个双向链表。
head和tail就是链表的头和尾
name2ctx维持了一个ChannelHandler 名字和Context映射关系,可以按照名字删除或添加
方法:
//该方法是所有handler处理的源头流入整个整个链表,想要触发后续的handler必须调用该方法
public void sendDownstream(ChannelEvent e){
//从链表头往后找实现ChannelUpstreamHandler接口的ChannelHandler
DefaultChannelHandlerContext head = getActualUpstreamContext(this.next);
sendUpstreamHandler(head,e)
}
public void sendUpstream(ChannelEvent e){
//从链表尾往头部找实现ChannelDownstreamHandler接口的ChannelHandler
DefaultChannelHandlerContext tail = getActualDownstreamContext(this.tail);
//首选判断 tail是否为null
if(tail == null){
//调用ChannelSink处理事件
getSink().eventSunk(this,e);
return;
}catch (Throwable t) {
notifyHandlerException(e, t);
return;
}
}
sendDownstream(tail, e);
}
public void sendUpstream(ChannelHandlerContext ctx,ChannelEvent e){
//大致逻辑
(ChannelUpstreamHandler)ctx.getHandler()).handlerUpstream(ctx,e);
}
void sendDownstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
if (e instanceof UpstreamMessageEvent) {
throw new IllegalArgumentException("cannot send an upstream event to downstream");
}
try {
((ChannelDownstreamHandler) ctx.getHandler()).handleDownstream(ctx, e);
} catch (Throwable t) {
// Unlike an upstream event, a downstream event usually has an
// incomplete future which is supposed to be updated by ChannelSink.
// However, if an exception is raised before the event reaches at
// ChannelSink, the future is not going to be updated, so we update
// here.
e.getFuture().setFailure(t);
notifyHandlerException(e, t);
}
}
DefaultChannelPipeline内部类:
在实际实现ChannelUpstreamHandler或ChannelDownstreamHandler时,调用 ChannelHandlerContext中的sendUpstream或sendDownstream方法将控制流程交给下一个 ChannelUpstreamHandler或下一个ChannelDownstreamHandler,或调用Channel中的write方法发送响应消息
public class MyChannelUpstreamHandler implements ChannelUpstreamHandler {
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
// handle current logic, use Channel to write response if needed.
// ctx.getChannel().write(message);
ctx.sendUpstream(e);
}
}
public class MyChannelDownstreamHandler implements ChannelDownstreamHandler {
public void handleDownstream(
ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
// handle current logic
ctx.sendDownstream(e);
}
}
private class DefaultChannelHandlerContext implements ChannelHandlerContext{
DefaultChannelHandlerContext next;
DefaultChannelHandlerContext prev;
String name;
ChannelHandler handler;
//该方法是在具体的Handler中处理完逻辑后进行显示的调用 ctx.sendUpstreamHandler(e)方法将事件传递到下个handler进行处理
public void sendUpstream(ChannelEvent e){
//从链表中获取UpstreamHandler
DefaultChannelHandlerContext next = getActualUpstreamContex(this.next);
//实际上还是调用了piple类中的 sendUpstreamHandler方法
DefaultChannelPipeline.this.sendUpstreamHandler(this.next,e);
}
//如果到达链尾,则将ChannelEvent发送给ChannelSink
public void sendDownstream(ChannelEvent e) {
DefaultChannelHandlerContext prev = getActualDownstreamContext(this.prev);
if (prev == null) {
try {
getSink().eventSunk(DefaultChannelPipeline.this, e);
} catch (Throwable t) {
notifyHandlerException(e, t);
}
} else {
DefaultChannelPipeline.this.sendDownstream(prev, e);
}
}
}
正是由于这种实现,如果在一个末尾的ChannelUpstreamHandler first中先移除自己,在向末尾添加一个新的ChannelUpstreamHandler second,它是无效的,因为first的next已经在调用前就固定设置为null了。所以再调用 ctx.sendUpstream(e) 方法时去找first的下一个结点,发现为null所以后面的second Handler就不会执行了
解决方法: 在remove frist之前调用 pipeline.addLast(handler) 或者在first 和 second之间存在至少一个ChannelHandler,也就是 first不是最后一个ChannelHandler
ChannelPipeline作为ChannelHandler的容器,它还提供了各种增、删、改ChannelHandler链表中的方法,而且如果某个ChannelHandler还实现了LifeCycleAwareChannelHandler,则该ChannelHandler在被添加进ChannelPipeline或从中删除时都会得到通知:
public interface LifeCycleAwareChannelHandler extends ChannelHandler {
void beforeAdd(ChannelHandlerContext ctx) throws Exception;
void afterAdd(ChannelHandlerContext ctx) throws Exception;
void beforeRemove(ChannelHandlerContext ctx) throws Exception;
void afterRemove(ChannelHandlerContext ctx) throws Exception;
}
public interface ChannelPipeline {
void addFirst(String name, ChannelHandler handler);
void addLast(String name, ChannelHandler handler);
void addBefore(String baseName, String name, ChannelHandler handler);
void addAfter(String baseName, String name, ChannelHandler handler);
void remove(ChannelHandler handler);
ChannelHandler remove(String name);
T remove(Class handlerType);
ChannelHandler removeFirst();
ChannelHandler removeLast();
void replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler);
ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler);
T replace(Class oldHandlerType, String newName, ChannelHandler newHandler);
ChannelHandler getFirst();
ChannelHandler getLast();
ChannelHandler get(String name);
T get(Class handlerType);
ChannelHandlerContext getContext(ChannelHandler handler);
ChannelHandlerContext getContext(String name);
ChannelHandlerContext getContext(Class extends ChannelHandler> handlerType);
void sendUpstream(ChannelEvent e);
void sendDownstream(ChannelEvent e);
ChannelFuture execute(Runnable task);
Channel getChannel();
ChannelSink getSink();
void attach(Channel channel, ChannelSink sink);
boolean isAttached();
List getNames();
Map toMap();
}
图-netty-channelpipeline
在ChannelHandler可能会通过 ctx.getChannel.write(msg) 向Channel中写入数据,这个时候就从UpstreamHandler链 转到 DownstreamHandler链进行处理
图-netty-channelpipeline-flow
该图直接从源码中复制的,不过在下游事件中增加了一个ChannelSink,这个是所以Downstream Event必经的一个组件,主要就是将message写入Socket中,将数据传输到网络中。 当然还包括一些对NIO Channel操作,例如bind connect close Channel事件也都会进入到ChannelSink,然后继续将事件委托给其他的类处理(NioWorker或Boss)
图-netty-channel-flow
可很直观看到ChannelEvent通过ChannelPipeline流经各个ChannelHandler进行逻辑处理。
后面我会对各几大模块(引导类、ChannelPipeline、Channel、I/O模型)用具体代码深入理解Netty的实现。
我从Netty3揭开背后的面纱的原因是:Netty4、5的引用的Reactor模式基本都一样,只要把Netty3摸清楚了,对于新版本的也是有很大作用的。更多的是,通过阅读Netty3源码就更加深入理解Netty4中的某些特性。还有更能看到Netty3存在哪些性能或其他问题,在Netty4是如何解决的,有助于把某些好的点子延伸到我们的项目中。
例如:在Netty3中,同个Channel中的下游ChannelHandler中如果没有实现同步的话,就会导致线程安全问题,因为有可能多个线程同时往Channel写入message,那么就会出现线程并发经过DownstreamChannelHandler,由于上游消息事件是由I/O线程来触发的,一个Channel对应于一个线程,所以当前事件处理结束之前就不会有下个事件。在Netty4中,就对此做了优化,将出站事件和入站事件都交给一开始和Channel绑定的EventLoop进行处理(I/O线程)
1、为了避免线程的切换(例如在上游处理过程中产生了异常,那么就会下发一个ExceptionDownstreamEvent交给DownstreamChannelHandler来处理最后会由I/O线程进行继续产生上游异常事件,进而导致线程的切换)
2、避免线程的同步
并且我会把Netty3以及Netty4相关的代码笔记上传到代码库中,以供大家参考Netty源码笔记。