1. Reactor模型
1.1 单线程reactor

一个单线程去维护所有的acceptor、read、decode、compute、encode、send。
1.2 多线程reactor模型

我们认为decode、read、compute比较耗时,使用一个线程池去管理。
1.3 主从多线程模型

上面有两个reactor,因为所有事件里面acceptor最重要,用一个单独的reactor去完成这个事件剩下的事件和1.2就比较相似了。
三种reactor模式的使用。

2. 问题一:Netty如何支持主从reactor模型
57行的b.group(bossGroup, workerGroup)就是上面说的主从reactor模式

跟进源码发现首先是调用了父类的super.group(parentGroup)方法,然后是在88行初始化了ServerBootstrap类中的全局变量childGroup

2.1 跟进group方法的81行,查看parentGroup
跟进81行代码,进入父类的group()方法,发现这个方法只是初始化了AbstractBootstrap类中的group全局变量

继续在AbstractBootstrap类中搜索group的使用,发现326行中出现了,将ServerSocketChannel绑定到parentGroup上的操作(parentGroup也就是workGroup)。

2.2 回到ServerBootstrap类中查看childGroup
在void init(Channel channel)方法中
1.childGroup被当作参数赋值给了currentChildGroup
2.currentChildGroup又被当作参数传入了ServerBootstrapAcceptor中

跟进159行代码,ServerBootstrapAcceptor是ServerBootstrap的一个内部类1.ServerBootstrapAcceptor内部也有一个childGroup
2.在ServerBootstrapAcceptor的构造函数中,传入的childGroup被当作参数赋值给了
ServerBootstrapAcceptor内部的全局变量childGroup

ServerBootstrapAcceptor中有一个channelRead()方法,里面将某个名为child的channel注册到了childGroup中

总结:bossGroup和workGroup分别注册了不同的channel在上面,以此完成一个主从reactor模型
3. 问题二:为什么main reactor大都只能用到一个线程
AbstractBootstrap保存了名叫group的对象保存的是是parentGroup的全局变量

而通过AbstractBootstrap的group()方法可以返回parentGroup变量

最后发现查看group方法的调用栈,发现会调用被doBind方法调用,而我们channel初始化只会绑定一次端口,所以我们需要只需要使用到一个线程。

4. 问题三:Netty给channel分配NIO EventLoop的原则是什么
通过下图里可以看出EventLoopGroup其实就是一个EventExecutorGroup

回到ServerBootstrap,ServerBootstrap的childGroup本质上是一个EventLoopGroup,那么他是如何使用 childGroup.register(child)完成将某个channel与一个EventExecutor之间的绑定起来处理channel的所有任务的呢?

跟进register()方法的源码,选择实现类MultithreadEventLoopGroup

上一步会跳转到如下界面

继续跟进next()方法

继续跟进super.next(),发现其使用了一个选择器choose来实现

继续跟进源码,发现其有两个实现类PowerOfTwoEventExecutorChooser和GenericEventExecutorChooser

再次进入其中某一个实现类发现上面两个实现类都是DefaultEventExecutorChooserFactory中的内部类。

他们选择使用哪一个内部类作为选择器的方法如下所示:具体选择方式请看代码中的注释

选择器选择使用哪一个EventExcutor去处理channel中的事件的方式如下:
下面中两个next()方法就是两个内部类选择的方式的源码,详情见注释。

总结:EventLoopGroup选择某一个EventExecutor去与channel绑定去处理channel中的事件的方式就是hash的方式。只是做出了优化,因为&运算比%运算效率高,如果EventExecutor[] executors这个处理器的数组的长度为2的幂次方,那么可以使用idx.getAndIncrement() & executors.length - 1的方式实现,否则使用%运算实现。
5.通用模式的NIO实现多路复用器是如何实现跨平台的
查看NioEventLoopGroup的构造函数,跟进代码SelectorProvider.provider()

发现三个方法,可以获取SelectorProvider
1.loadProviderFromProperty方法
2.loadProviderAsService方法
3.sun.nio.ch.DefaultSelectorProvider.create()方法

5.1 跟进loadProviderFromProperty方法
发现是反射的方式去获取SelectorProvider

5.2 跟进loadProviderAsService方法
使用的是SPI的机制实例化对象

5.3 跟进sun.nio.ch.DefaultSelectorProvider.create()方法
发现里面会new WindowsSelectorProvider()来创建一个windows平台下的SelectorProvider

总结: 跨平台的实现是通过sun.nio.ch.DefaultSelectorProvider.create()方法实现的,不同平台下,new出来的SelectorProvider对象不同。
本文深入探讨Netty中的Reactor模型,包括单线程、多线程及主从多线程模型的实现原理。详细解析了Netty如何支持主从reactor模型,为何main reactor大都只能用到一个线程,以及Netty给channel分配NIOEventLoop的原则。同时,介绍了NIO实现多路复用器的跨平台实现机制。

1558

被折叠的 条评论
为什么被折叠?



