一直对Java高并发比较感兴趣,最近在学习netty特来记录。
提到netty首先要了解一下什么是Reactor设计模式,在网上找了一下关于Reactor设计模式的介绍,链接在文档的最下方。
在《Scalable IO in Java》中讲到了一种多线程下的Reactor模式。
在这种设计模式中,有一个前端mainReactor主要负责响应client的连接请求并建立连接,和一个后端subReactor。mainReactor接收client的请求之后会转接给subReactor。由subReactor负责具体的客户端请求的读写工作。这样有一个好处,subReactor可以做一些比较耗时的操作,可以减少CPU因等待IO而阻塞的时间。
知道了什么是Reactor设计模式之后,我们来看看Reactor设计模式在netty中的应用.Netty里对应mainReactor的角色叫做“Boss”,而对应subReactor的角色叫做”Worker”。Boss负责分配请求,Worker负责执行。
在看源码之前,要首先理解几个概念:
EventLoopGroup:一个EventLoopGroup就是一组EventLoop。主要负责管理EventLoop的申请和释放。
EventLoop:处理所有的IO操作。EventLoop继承了EventLoopGroup接口,可以被当做一个single的线程池看待。
源码分析从Netty版的Hello World的开始
代码片段的前两行就是创建bossGroup和workerGroup对象。是不是感觉有点似曾相识,还记得上文我们提到的mainReactor和subReactor嘛。bossGroup起到了mainReactor的作用,workerGroup起到了subReactor的作用。
NioEventLoopGroup类继承体系如下:
从图中发现,NioEventLoopGroup既然继承了Executor,是不是猜想NioEventLoopGroup是怎么优雅的使用多线程的。
NioEventLoopGroup构造函数:
这里无非是对构造函数的封装,而最终调用父类MultithreadEventLoopGroup的构造函数
这里会判断传进来的线程数是否是0。如果是0就使用默认的线程数。
默认取-Dio.netty.eventLoopThreads,如果该系统参数也没有指定,则为可用的CPU内核数 × 2。在不需要监听多端口的情况下,bossGroup只需要1个线程即可。MultithreadEventLoopGroup构造函数会显示调用MultithreadEventExecutorGroup的构造函数
为了便于理解,去掉了一些验证逻辑和出错处理逻辑。
1.如果没有指定excutor,会先指定executor。
2.接着创建children数组,现在可以知道EventLoopGroup和EventLoop[]之间的关系。
3.通过for循环实例children数组。
在SingleThreadEventExecutor构造函数中会初始化一个任务队列,netty线程一直是在死循环中去直接IO事件和非IO事件,除了IO事件,所有非IO事件都是先丢到这个任务队列中,然后在由work线程去执行。
openSeletor会先创建selector选择器,这里完全是JDK NIO的Selector的使用方法,不在累述。
接着DISABLE_KEYSET_OPTIMIZATION是判断是否需要对sun.nio.ch.SelectorImpl中的selectedKeys进行优化, 不做配置的话默认需要优化。
SelectorImpl中的selectedKeys和publicSelectedKeys是个HashSet, 新的数据结构是双数组A和B, 初始大小1024, 避免了HashSet的频繁自动扩容, processSelectedKeys时先使用数组A,再一次processSelectedKeys时调用flip的切换到数组B, 如此反复 。为什么要做这些优化我也不是很清楚。
参考资料:
Reactor设计模式介绍:
http://www.blogjava.net/DLevin/archive/2015/09/02/427045.html
多线程Reactor:
http://my.oschina.net/flashsword/blog/197963