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
对象不同。