Netty 实现原理浅析
Netty 是 JBoss 出品的高效的 Java NIO 开发框架,关于其使用,可参考我的另
一篇文章 netty 使用初步。本文将主要分析 Netty 实现方面的东西,由于精力
有限,本人并没有对其源码做了极细致的研 究。如果下面的内容有错误或不严
谨的地方,也请指正和谅解。对于 Netty 使用者来说,Netty 提供了几个典型的
example,并有详尽的 API doc 和 guide doc,本文的一些内容及图示也来自于
Netty 的文档,特此致谢。
1、总体结构
先放上一张漂亮的 Netty 总体结构图,下面的内容也主要围绕该图上的一些核心
功能做分析,但对如 Container Integration 及 Security Support 等高级可选
功能,本文不予分析。
2、网络模型
Netty 是典型的 Reactor 模型结构,关于 Reactor 的详尽阐释,可参考 POSA2,
这里不做概念性的解释。而应用 Java NIO 构建 Reactor 模式,Doug Lea(就是
那位让人无限景仰的大爷)在“Scalable IO in Java”中给了很好的阐述。这
里截取其 PPT 中经典的图例说明 Reactor 模式的典型实现:
1、这是最简单的单 Reactor 单线程模型。Reactor 线程是个多面手,负责多路
分离套接字,Accept 新连接,并分派请求到处理器链中。该模型 适用于处理器
链中业务处理组件能快速完成的场景。不过,这种单线程模型不能充分利用多核
资源,所以实际使用的不多。
2、相比上一种模型,该模型在处理器链部分采用了多线程(线程池),也是后
端程序常用的模型。
3、 第三种模型比起第二种模型,是将 Reactor 分成两部分,mainReactor 负责
监听 server socket,accept 新连接,并将建立的 socket 分派给 subReactor。
subReactor 负责多路分离已连接的 socket,读写网 络数据,对业务处理功能,
其扔给 worker 线程池完成。通常,subReactor 个数上可与 CPU 个数等同。
说完 Reacotr 模型的三种形式,那么 Netty 是哪种呢?其实,我还有一种 Reactor
模型的变种没说,那就是去掉线程池的第三种形式的变种,这也 是 Netty NIO
的默认模式。在实现上,Netty 中的 Boss 类充当 mainReactor,NioWorker 类充
当 subReactor(默认 NioWorker 的个数是
Runtime.getRuntime().availableProcessors())。在处理新来的请求 时,
NioWorker 读完已收到的数据到 ChannelBuffer 中,之后触发 ChannelPipeline
中的 ChannelHandler 流。
Netty 是事件驱动的,可以通过 ChannelHandler 链来控制执行流向。因为
ChannelHandler 链的执行过程是在 subReactor 中同步的,所以如果业务处理
handler 耗时长,将严重影响可支持的并发数。这种模型适合于像 Memcache 这
样的应用场景,但 对需要操作数据库或者和其他模块阻塞交互的系统就不是很
合适。Netty 的可扩展性非常好,而像 ChannelHandler 线程池化的需要,可以
通过在 ChannelPipeline 中添加 Netty 内置的 ChannelHandler 实现类
–ExecutionHandler 实现,对使用者来说只是 添加一行代码而已。对于
ExecutionHandler 需要的线程池模型,Netty 提供了两种可 选:1)
MemoryAwareThreadPoolExecutor 可控制 Executor 中待处理任务的上限(超过
上限时,后续进来的任务将被阻 塞),并可控制单个 Channel 待处理任务的上
限;2) OrderedMemoryAwareThreadPoolExecutor
是 MemoryAwareThreadPoolExecutor 的子类,它还可以保证同一 Channel 中处
理的事件流的顺序性,这主要是控制事件在异步处 理模式下可能出现的错误的
事件顺序,但它并不保证同一 Channel 中的事件都在一个线程中执行(通常也没
必要)。一般来 说,OrderedMemoryAwareThread
择,当然,如果有需要,
,也可以 DIY 一个。
OrderedMemoryAwareThreadPoolExecutor 是个很不错的选
是个很不错的选
3、 buffer
org.jboss.netty.buffer
org.jboss.netty.buffer 包的接口及类的结构图如下:
该包核心的接口是 ChannelBuffer
绍。
ChannelBuffer 和 ChannelBufferFactory,下面予以简要的介
下面予以简要的介
Netty 使用 ChannelBuffer
提供和 ByteBuffer 类似的方法
文档。ChannelBuffer 的
ChannelBuffer 来存储并操作读写的网络数据。ChannelBuffer
类似的方法,还提供了 一些实用方法,具体可参考其
ChannelBuffer 除了
具体可参考其 API
的实现类有多个,这里列举其中主要的几个
这里列举其中主要的几个:
ChannelBuffer,
ByteBuffer 实际操作的就是个 byte 数组,所以
所以
的内部就包含了一个 byte 数组,使得 ByteBuffer
之间的转换是零拷贝方式。根据网络字 节续的不同
:这是 Netty 读网络数据时默认使用的 ChannelBuffer
堆的意思,因为 读 SocketChannel 的数据是要经过
的数据是要经过
1)HeapChannelBuffer:
这里的 Heap 就是 Java 堆的意思
ByteBuffer 的,而 ByteBuffer
ChannelBuffer 的内部就包含了一个
ChannelBuffer 之间的转换是零拷贝方式
HeapChannelBuffer 又分为
LittleEndianHeapChannelBuffer,默认使用的是 BigEndianHeapChannelBuffer
LittleEndianHeapChannelBuffer
Netty 在读网络 数据时使用的就是
HeapChannelBuffer 是
大小不太合适,Netty 在分
个大小固定的 buffer,为了不至于分配的
配 Buffer 时会参考上次请求需要的大小
时会参考上次请求需要的大小。
数据时使用的就是 HeapChannelBuffer,HeapChannelBuffer
为了不至于分配的 Buffer 的 大小不太合适
又分为 BigEndianHeapChannelBuffer 和
ByteBuffer 和
节续的不同,
BigEndianHeapChannelBuffer。
2)DynamicChannelBuffer:相比于 HeapChannelBuffer,DynamicChannelBuffer
可动态自适应大 小。对于在 DecodeHandler 中的写数据操作,在数据大小未知
的情况下,通常使用 DynamicChannelBuffer。
3)ByteBufferBackedChannelBuffer:这是 directBuffer,直接封装了
ByteBuffer 的 directBuffer。
对于读写网络数据的 buffer,分配策略有两种:1)通常出于简单考虑,直接分
配固定大小的 buffer,缺点是,对一些应用来说这个大小限制有时是不 合理的,
并且如果 buffer 的上限很大也会有内存上的浪费。2)针对固定大小的 buffer
缺点,就引入动态 buffer,动态 buffer 之于固定 buffer 相当于 List 之于 Array。
buffer 的寄存策略常见的也有两种(其实是我知道的就限于此):1)在多线程
(线程池) 模型下,每个线程维护自己的读写 buffer,每次处理新的请求前清
空 buffer(或者在处理结束后清空),该请求的读写操作都需要在该线程中完
成。 2)buffer 和 socket 绑定而与线程无关。两种方法的目的都是为了重用
buffer。
Netty 对 buffer 的处理策略是:读 请求数据时,Netty 首先读数据到新创建的
固定大小的 HeapChannelBuffer 中,当 HeapChannelBuffer 满或者没有数据可读
时,调用 handler 来处理数据,这通常首先触发的是用户自定义的 DecodeHandler,
因为 handler 对象是和 ChannelSocket 绑定的,所以在 DecodeHandler 里可以
设置 ChannelBuffer 成员,当解析数据包发现数据不完整时就终止此次处理流程,
等下次读事件触 发时接着上次的数据继续解析。就这个过程来说,和
ChannelSocket 绑定的 DecodeHandler 中的 Buffer 通常是动态的可重用 Buffer
(DynamicChannelBuffer),而在 NioWorker 中读 ChannelSocket 中的数据的
buffer 是临时分配的 固定大小的 HeapChannelBuffer,这个转换过程是有个字
节拷贝行为的。
对 ChannelBuffer 的创建,Netty 内部使用的是 ChannelBufferFactory 接口,
具体的实现有 DirectChannelBufferFactory 和 HeapChannelBufferFactory。对
于开发者创建 ChannelBuffer,可使用实用类 ChannelBuffers 中的工厂方法。
4、Channel
和 Channel 相关的接口及类结构图如下:
从该结构图也可以看到,
,Channel 主要提供的功能如下:
的状态信息,比如是打开还是关闭等。
ChannelConfig 可以得到的 Channel 配置信息。
1)当前 Channel 的状态信息
2)通过 ChannelConfig
3)Channel 所支持的如
4)得到处理该 Channel
IO 操作。
所支持的如 read、write、bind、connect 等 IO 操作。
。
Channel 的 ChannelPipeline,既而可以调用其做和请求相关的
既而可以调用其做和请求相关的
在 Channel 实现方面,以通常使用的
NioServerSocketChannel
NioServerSocketChannel 和 NioSocketChannel 分别封装了 java.nio
ServerSocketChannel 和
以通常使用的 nio socket 来说,Netty 中的
中的
和 SocketChannel 的功能。
java.nio 中包含的
5、ChannelEvent
ChannelEvent
如前所述,Netty 是事件驱动的
一个 ChannelEvent 是依附于
ChannelPipeline 调用 ChannelHandler
ChannelEvent 相关的接口及类图
相关的接口及类图:
是事件驱动的,其通过 ChannelEvent 来确定事件流的方向
来确定事件流的方向。
是依附于 Channel 的 ChannelPipeline 来处理
来处理,并由
ChannelHandler 来做具体的处理。下面是和
下面是和
对于使用者来说,在 ChannelHandler
MessageEvent,调用其
化的对象。
ChannelHandler 实现类中会使用继承于 ChannelEvent
ChannelEvent 的
getMessage()方法来获得读到的 ChannelBuffer
ChannelBuffer 或被转
6、ChannelPipeline
ChannelPipeline
Netty 在事件处理上,是通过
上的一系列 ChannelHand
ChannelPipeline 相关的接口及类图
相关的接口及类图:
ChannelHandler 来处理事件,这也是典型的拦截 器模式
是通过 ChannelPipeline 来控制事件流,通过调用注册其
通过调用注册其
器模式。下面是和
ChannelHandler 既可以 是 ChannelUpstreamHandler
ChannelUpstreamHandler 也可以是
ChannelPipeline 中,其
upstream 事件和 downstream 事件。在 ChannelPipeline
事件流有两种,upstream
可被注册的 ChannelHandler
ChannelDownstreamHandler ,但事件在 ChannelPipeline 传递过程中只会调用
ChannelDownstreamHandler
传递过程中只会调用
匹配流的 ChannelHandler
ChannelHandler。在事件流的过滤器链 中,ChannelUpstreamHandler
elUpstreamHandler
或 ChannelDownstreamHandler
ChannelHandlerContext.sendUpstream(ChannelEvent)
ChannelHandlerContext.sendUpstream(ChannelEvent)或
ChannelHandlerContext.sendDownstream(ChannelEvent)
ChannelHandlerContext.sendDownstream(ChannelEvent)将事件传递下去
是事件流处理的图示:
ChannelDownstreamHandler 既可以终止流程,也可以通过调用
也可以通过调用
将事件传递下去。下面