之前在看rocketmq源码时,发现底层用了Netty,顺便学习了一下,网上不少博客讲的有错误之处,而且大部分一模一样,估计大部分都是复制别人的。为了不被误导,我专门买了本《Netty权威指南》,仔细阅读了一遍,而且请教了锋哥(李林锋),遂整理出这篇分享。
本人一直秉承原则:宁愿不写、少写,也尽量不写错的知识!以免误人子弟!
希望转载的同学,标出原文链接。谢谢!同时非常欢迎指出错误,本人及时修正!
版本说明
Netty3(3.x)版本是比较旧的版本。
Netty4(4.x)版本是当前官方推荐的,目前一直在维护中。跟3.x版本相比变化比较大,特别是API。
Netty5(5.x)是被舍弃的版本,官方不推荐使用!
Netty5舍弃的官方解释:
1. netty5中使用了 ForkJoinPool,增加了代码的复杂度,但是对性能的改善却不明显
2. 多个分支的代码同步工作量很大
3. 作者觉得当下还不到发布一个新版本的时候
4. 在发布版本之前,还有更多问题需要调查一下,比如是否应该废弃 exceptionCaught,是否暴露EventExecutorChooser等等。
参考:https://github.com/netty/netty/issues/4466
Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。
作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于Netty的NIO框架构建。如:Dubbo、 RocketMQ、Hadoop的Avro、Spark等。
Netty需要学习的内容: 编解码器、TCP粘包/拆包及Netty如何解决、ByteBuf、Channel和Unsafe、ChannelPipeline和ChannelHandler、EventLoop和EventLoopGroup、Future等。
编解码器:
Java序列化的目的主要有两个:
Java序列化仅仅是Java编解码技术的一种,由于他的种种缺陷,衍生出多种编解码技术和框架。
Java序列化的缺点:
业界主流的编解码框架:
TCP粘包/拆包:
TCP是个“流”协议,所谓流,就是没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓存区的实际情况进行包的划分,所以在业务上的一个完整的包,可能被TCP拆分成多个包进行发送,也可能把多个小包封装成一个大的数据包发送,这就是TCP粘包和拆包问题。
有如下几种情况:
正常情况
粘包
粘包和拆包同时发生
在JAVA NIO方面Selector给Reactor模式提供了基础,Netty结合Selector和Reactor模式设计了高效的线程模型。
关于Java NIO 构造Reator模式,Doug Lea在《Scalable IO in Java》中给了很好的阐述,这里截取PPT对Reator模式的实现进行说明。
(1)Reactor单线程模型
这是最简单的Reactor单线程模型,由于Reactor模式使用的是异步非阻塞IO,所有的IO操作都不会被阻塞,理论上一个线程可以独立处理所有的IO操作。这时Reactor线程是个多面手,负责多路分离套接字,Accept新连接,并分发请求到处理链中。
对于一些小容量应用场景,可以使用到单线程模型。但对于高负载,大并发的应用却不合适,主要原因如下:
1. 当一个NIO线程同时处理成百上千的链路,性能上无法支撑,即使NIO线程的CPU负荷达到100%,也无法完全处理消息。
2. 当NIO线程负载过重后,处理速度会变慢,会导致大量客户端连接超时,超时之后往往会重发,更加重了NIO线程的负载。
3. 可靠性低,一个线程意外死循环,会导致整个通信系统不可用。
为了解决这些问题,出现了Reactor多线程模型。
(2)Reactor多线程模型
相比上一种模式,该模型在处理链部分采用了多线程(线程池)。
在绝大多数场景下,该模型都能满足性能需求。但是,在一些特殊的应用场景下,如服务器会对客户端的握手消息进行安全认证。这类场景下,单独的一个Acceptor线程可能会存在性能不足的问题。为了解决这些问题,产生了第三种Reactor线程模型。
(3)Reactor主从模型
该模型相比第二种模型,是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接;并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,读写网络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同。
利用主从NIO线程模型,可以解决一个服务端监听线程无法有效处理所有客户端连接的性能不足问题,因此,在Netty的官方Demo中,推荐使用该线程模型。
(4)Netty模型
Netty的线程模型并不是一成不变,它实际取决于用户的启动参数配置。通过设置不同的启动参数,Netty可以同时支持Reactor单线程模型、多线程模型和主从模型。
前面介绍完 Netty 相关一些理论,下面从功能特性、模块组件来介绍 Netty 的架构设计。
下图是Netty的功能特性:
Netty主要有下面一些组件:
Selector
Netty 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件。
NioEventLoop
其中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务。
NioEventLoopGroup
主要管理 eventLoop 的生命周期,可以理解为一个线程池,内部维护了一组线程,每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。
ChannelHandler
是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。
ChannelHandlerContext
保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。
ChannelPipeline 是保存 ChannelHandler 的 List,用于处理或拦截 Channel 的入站事件和出站操作。实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何相互交互。
ChannelPipeline对事件流的拦截和处理流程:
Netty中的事件分为Inbond事件和Outbound事件。
Inbound事件通常由I/O线程触发,如TCP链路建立事件、链路关闭事件、读事件、异常通知事件等。
Outbound事件通常是用户主动发起的网络I/O操作,如用户发起的连接操作、绑定操作、消息发送等。
在 Netty中,Channel 、ChannelHandler、ChannelHandlerContext、 ChannelPipeline的关系如下图:
一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。
入站事件和出站事件在一个双向链表中,入站事件会从链表 head 往后传递到最后一个入站的 handler,出站事件会从链表 tail 往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰。
以上是Netty的主要原理介绍,Netty源码分析的话,后续有时间会继续分享出来。