今天是2020年2月2号,感觉是一个比较特殊的日子,今天就来一篇记录型的博客吧,哈哈
起初很好奇,到底什么叫Reactor模式,这个名词感觉特别高大上,然后看描述,虽然能看懂描述,但是却不是特别明白到底是什么意思。这个时候主要是没有形成一种直观的印象,直观的印象就是比如说苹果,再给你看个实物,你就能把苹果与关联起来。在学习netty的时候,也遇到了Reactor模式,于是有了机会来形成一种比较直观的印象。
什么是Reactor模式?
定义看起来很抽象,但是其实很好理解。它是一种开发模式,模式的核心流程:注册感兴趣的事件 -> 扫描是否有感兴趣的事件发生 -> 事件发生后做出相应的处理。仅此而已。使用BIO开发的时候,每有一个新的请求过来了,都会新开一个线程,然后在新的线程里面进行业务处理,这种处理方式就是Thread-Per-Connection;
所以对应起来,使用NIO开发的时候,也有一个模式去处理相应的请求与业务逻辑,叫做Reactor模式。至于具体怎么做,也就是前面提到的Reactor模式的核心流程。
Reactor模式的3种版本
开始这个之前我有一个疑问:Thread-Per-Connection与Reactor单线程有什么关系?
Thread-Per-Connection模式
示意图:
伪代码:
Reactor单线程模式
从这张图里面看不懂其执行流是什么样的。待后续理解了再补上解读。
Reactor多线程模式
主从Reactor多线程模式
对服务器开发来说,很重要的事情是接收连接,accept事件会被单独注册到另外一个reactor中。
在Netty中如何实现Reactor三种模式
其中单线程和非主从reactor多线程模式的差别只在于new的时候传入的线程数量,不传的话,会默认以CPU的核数为依据来确定最终的线程数。
Netty 如何支持主从 Reactor 模式
以netty项目源代码(分支4.1)中netty-example模块的EchoServer
为例。
保存
它是一个主从reactor多线程模式,其中bossGroup
负责accept事件,workerGroup
负责逻辑处理。
在①中,分别将两个EventLoopGroup
传入到ServerBootstrap
中,并将这两个EventLoopGroup
保存起来。
步骤②执行的保存逻辑如下:
步骤③即已保存完毕。保存起来之后,什么时候使用呢?
将channel注册到parentGroup
先看parentGroup
的使用过程,找到使用了group
这个变量的地方Ctrl + B
:
进去之后,是一个类似于普通getter方法
只有一个地方调用,名称也叫group()
,所以还可以继续往上看调用者
然后使用Ctrl + Alt + H
查看该group()
方法的调用者:
在initAndRegister()
中可以找到将channel
(即ServerSocketChannel
)注册到该EventLoopGroup
的代码,如下:
绑定完毕。
将channel
注册到childGroup
找到使用了childGroup
这个变量的地方Ctrl + B
:
只有个地方使用到了该childGroup
,并改名成了currentChildGroup
。
①改名,②将childGroup
作为一个变量,传入ServerBootstrapAcceptor
中。ServerBootstrapAcceptor
继承自ChannelInboundHandlerAdapter
,其覆盖了父类的channelRead()
方法,其中将新进来的channel
注册到childGroup
中。
也就是说,新进来的连接,即SocketChannel
,都会被注册到childGroup
中。
新连接建立进行哪些初始化
回到上面的init()
方法中提出的,它是何时被调用的这个问题中。
它是AbstractBootstrap
抽象类中的一个抽象方法,有两个类继承自AbstractBootstrap
,分别是Bootstrap
和ServerBootstrap
。调用init()
方法的地方只有一个,即initAndRegister()
中。
其中传入init()
的channel
为ServerSocketChannel
,其大致过程:
当服务端即EchoServer
启动的时候,会为ServerSocketChannel
的pipeline添加一个ServerBootstrapAcceptor
,所以每当有来自客户端的请求时,都会首先经过ServerBootstrapAcceptor
,让它先处理,而它的处理内容就是将SocketChannel
注册到childGroup
中。
为什么说 Netty 的 main reactor 大多并不能用到一个线程组,只能线程组里面的一个?
因为服务端只会启动一次,只有在启动过程中去绑定端口号时才会将ServerSocketChannel
绑定到main reactor
上。所以这时候要从initAndRegister
的调用者逐级往上查看,如下;
1 | public ChannelFuture bind(InetAddress inetHost, int inetPort) { |
所以一个ServerSocketChannel
只会注册到一个group中。但还是个疑问,是与EventLoopGroup相关的,留待后续再来回答。这个问题的意思是说,只能用线程组里面的一个线程,为什么?为什么不能多个线程?下面这个问题可以回答这个疑问!
Netty 给 Channel 分配 NIO event loop 的规则是什么
从initAndRegister()
中的config().group().register(channel)
代码出发,也就是ServerSocketChannel
注册到main reactor中的那段代码(参见上面)。
1 | // 从register方法进入 |
所以chooser.next()
返回的是一个等价于Thread的对象,也就是说这个ServerSocketChannel
只会在这个Thread中进行接收。其中的chooser就是根据线程数的个数,来选取一个线程分配给register进来的ServerSocketChannel
。具体分配策略:
1 | // next()是一个抽象方法,它的具体实现有两种 |
至此,上面的那个疑问算是有了一个答案。