前言:
前面章节我们对Netty的整体结构和使用流程进行了剖析,使用过程中我们首先创建了两个线程组EventLoopGroup,一个负责连接分派,一个负责IO读写,那么这两个线程组工作原理是怎么样的呢?由于NioEventLoopGroup应用较为广泛,我们从这个线程组开刀!
结论:
由于文章源码冗长,如果你没兴趣看(我猜测你应该没兴趣),直接看结论。
一:NioEventLoopGroup构建的两个线程组boss和worker,线程个数默认为CPU个数的2倍。
二:NioEventLoopGroup中的(boss和worker)线程底层通过Java NIO中的Selector实现对通道的绑定和监听。
三:boss线程执行通道连接,当监听到read事件之后绑定worker线程和通道。worker线程执行通道的IO读写。
四:netty本质上还是NIO而不是AIO,只不过执行的线程我们可以指定。
1:Reactor模型:
Reactor模型分为三种,根据并发的用户量性能由低到高
第一种是单线程接收多客户端连接请求并亲自处理IO读写
第二种单线程接收多客户端连接请求请求,转发给其他线程池进行IO读写
第三种多线程接收多客户端连接请求,转发给其他线程池进行IO读写
Netty可以完全吸纳该模型,server端可以通过设置bossgroup和workergroup来选择使用第二种和第三种模型。
2:NioEventLoop线程模型:
我们用到的线程组EventLoopGroup workerGroup = new NioEventLoopGroup()实际上是有每个NioEventLoop线程模型组成的。
类似于web天生的多线程,只要每个业务逻辑的Handler(车间)是无状态的,那么所有的NioEventLoop(工人)就可以并发执行,而不需要加锁。
A:NioEventLoop原理分析:
![12062369-53858cc472e3c3ec.png](https://i-blog.csdnimg.cn/blog_migrate/06be3e4488a6f5682bcea5979f4dafa7.webp?x-image-process=image/format,png)
由该类图可以看出NioEventLoop线程模型顶层接口实现了ScheduledExecutorService,这是Java concurrent包里面的接口,该接口可以执行定时任务,所以毫无疑问,NioEventLoop模型也能执行任务并且能执行定时任务!
打开源码,我们发现NioEventLoop内部依赖了一个Selector,这个Selector是Java NIO中的Selector,所以可见Netty本质上并不是AIO而是NIO。
那么,NioEventLoop到底能干哪些事呢?
a:打开了Selector,run方法中,根据是否有要执行的任务来选择调用selectNow方法和select方法(该方法没有会轮询),值得深思的是一个线程模型打开一个Selector。
b:注册通道,注册通道方法register,通过该方法绑定通道和线程。
c:执行任务,run方法最终调用的processSelectedKey方法是真正执行任务的代码,该方法通过Java NIO中的SelectionKey和channel真正执行执行通道的IO读写操作。
以上过程是不是有种似曾相识的感觉?没错,就是Java NIO的操作流程的封装!
3:NioEventLoopGroup原理分析:
我们前面说了NioEventLoop能干什么,但是并没有说怎么干的,谁指使他干的?我们下面回答这个问题。
首先,我们新建NioEventLoopGroup线程组,追踪其源码调用链
![12062369-e30af94687c81cd8.png](https://i-blog.csdnimg.cn/blog_migrate/3496fa2aa6714046d95aa57d3bd4f4e9.webp?x-image-process=image/format,png)
![12062369-4accc30f40e81c7d.png](https://i-blog.csdnimg.cn/blog_migrate/4652d6f65534933a3fe9b3f17cdf7e3d.webp?x-image-process=image/format,png)
![12062369-8825cbf3f28a901e.png](https://i-blog.csdnimg.cn/blog_migrate/f986bf7397b259005878ac1d839675bf.webp?x-image-process=image/format,png)
![12062369-ae769b88eb64008d.png](https://i-blog.csdnimg.cn/blog_migrate/00df866a791d419f1b2b39bbd7540a21.webp?x-image-process=image/format,png)
![12062369-fd02ba9db7c61f35.png](https://i-blog.csdnimg.cn/blog_migrate/7109e238cd53f70fbbe2a3dd588ae2bb.webp?x-image-process=image/format,png)
![12062369-743736ed0cd8b94e.png](https://i-blog.csdnimg.cn/blog_migrate/304d5292d49dbc29844999aaea9de7a2.webp?x-image-process=image/format,png)
好,追踪到这里我们可以发现,在不填写参数的情况下创建的线程数是CPU个数的2倍!继续往下追踪.....
![12062369-68be2c0a6befab02.png](https://i-blog.csdnimg.cn/blog_migrate/3517726b3984e3671cddb60a677fe540.webp?x-image-process=image/format,png)
![12062369-3f845be517988d47.png](https://i-blog.csdnimg.cn/blog_migrate/0c19c1fe718e9e58f3902b86f2305ddb.webp?x-image-process=image/format,png)
调用链到这里只有nThreads是有值的,executor为空,所以新建了一个ThreadPerTaskExecutor,这个类本质上是一个Java的Executor线程执行器,可以执行任务,继续。。。。
![12062369-2a931f53018c2248.png](https://i-blog.csdnimg.cn/blog_migrate/4d1a15c3fbc1f2aee81aae444b8266c6.webp?x-image-process=image/format,png)
还是7环节中的构造方法,我们看到,这个MultiThreadEventLoopGroup内置了一个线程执行器数组,数组长度与线程个数相同,下面调用了newChild给每个执行器数组元素赋值,点击进入newChild方法,由于我们调用的是NioEventLoopGroup,所以进入NioEventLoop的实现。。。。
![12062369-0cbbb5effa2873e0.png](https://i-blog.csdnimg.cn/blog_migrate/a48440848220456b0f8b27f1e93da115.webp?x-image-process=image/format,png)
![12062369-b6179d5ec8c597f2.png](https://i-blog.csdnimg.cn/blog_migrate/f0cb5da0c88edf1d1ed0be2c79bce888.webp?x-image-process=image/format,png)
![12062369-cc737b07f758b4cf.png](https://i-blog.csdnimg.cn/blog_migrate/76f1a67978579b78d8ded6ab4437d6d8.webp?x-image-process=image/format,png)
看见没,这个new NioEventLoop(this,executor,(SelectorProvider)args[0]);方法,这就是最终NioEventLoopGroup线程组的组成元素,构造NioEventLoop过程中给它传入了我们的ThreadPerTaskExecutor执行器。最后这个调用链还调用了openSelector()启动Selector!!!
总结:压缩以上过程,NioEventLoopGroup通过内置EventExecutor数组而实现线程组,而EventExecutor数组内部元素是NioEventLoop,所以可以说NioEventLoopGroup在构造过程中创建了2*CPU个NioEventLoop待用!!!!
4:启动:
前面我们剖析了NioEventLoopGroup是怎么由NioEventLoop构成的,下面我们分析下NioEventLoop是怎么工作的!
![12062369-35b04a02ea919dfe.png](https://i-blog.csdnimg.cn/blog_migrate/3887bd7f87b956c81051feb37ca5e4b5.webp?x-image-process=image/format,png)
我们直接看图1,bootstrap.bind方法,点击进入调用链
![12062369-c81802a793f478e0.png](https://i-blog.csdnimg.cn/blog_migrate/9a2b2845550e11f0699293825f00dfa7.webp?x-image-process=image/format,png)
绑定IP和端口。。。
![12062369-d7fedbeaf64fa462.png](https://i-blog.csdnimg.cn/blog_migrate/26ebf44847d4d741bb0510746e9fcdad.webp?x-image-process=image/format,png)
![12062369-490563b7e61b31fc.png](https://i-blog.csdnimg.cn/blog_migrate/18d98babba6394ad6a2d5ae9e3584594.webp?x-image-process=image/format,png)
调用至此,首先initAndRegister方法初始化了一条通道,并把ChannelFuture预期也绑定到了通道上。...
![12062369-340f8ee9e970ac82.png](https://i-blog.csdnimg.cn/blog_migrate/d07767f6679e2ad8ec7070a7a678a433.webp?x-image-process=image/format,png)
可以看到initAndRegister创建了一条通道并对通道进行了初始化,这个过程实际上把Channel通道内部的eventLoop设置成了bossGroup中的NioEventLoop
![12062369-75be52c8bae9446a.png](https://i-blog.csdnimg.cn/blog_migrate/c7c39f856ab30401eff9ca0057935cf0.webp?x-image-process=image/format,png)
这是ServerBootstrap中的方法,group()方法返回的实际上是bossGroup,next()方法则从bossGroup中返回一个NioEventLoop。
直到目前,所有的工作还都是有main线程来执行的,channelFactory方法返回的是ServerBootstrapChannelFactory,newChannel方法创建的是NioServerSocketChannel(还记得你写的代码里.channel(NioServerSocketChannel)方法吗?这里的channel就是根据这个来的,具体源码略)
![12062369-c192ff7443aa5e01.png](https://i-blog.csdnimg.cn/blog_migrate/5292875454e6ddbe8ba6e858ecbf3da8.webp?x-image-process=image/format,png)
![12062369-c7a334ce68437f57.png](https://i-blog.csdnimg.cn/blog_migrate/b39566f342b91c60c2960f14dfe8bd2c.webp?x-image-process=image/format,png)
NioServerSocketChannel实例化的时候设置了感兴趣的通道事件为OP_ACCEPT!!!沿着这个实例化链,一直到AbstractChannel,最终给该实例设置了pipeline、unsafe、bossGroup的NioEventLoop三个成员变量。我们继续查看4.1环节中的channel.unsafe.register()方法,我们现在知道channel是我们刚刚创建的NioServerSocketChannel实例,unsafe方法返回的是该实例的成员变量,该实例内部还有一个bossGroup的线程模型实例。
![12062369-5a3e4b33f52ecfff.png](https://i-blog.csdnimg.cn/blog_migrate/0765b67f62ed2f7d56f9757c773656c5.webp?x-image-process=image/format,png)
这个register方法是AbstractChannel中的方法,所以eventLoop就是boss线程模型,boss线程模型执行register0方法,所以这个register方法绑定了通道与bossgroup线程!!!继续看init(channel)方法、、、
![12062369-29556cbd0073a506.png](https://i-blog.csdnimg.cn/blog_migrate/a5004bdb1f2d487a5148b8f2dbb3ba32.webp?x-image-process=image/format,png)
main线程执行的init方法是ServerBootstrap中的方法,其中handler()方法就是我们代码里写的.handler(handler)。init方法内部还创建了一条流水线pipeline,并在流水线里加入了一个ServerBootstrapAcceptor,注意,我们用的是netty5.0,这和netty4.0里是有变化的,netty5.0并没有直接给ServerBootstrapAcceptor设置一个workerGroup,而仅仅是设置了一些childHandler!!!
我们继续追踪,希望找到通道是怎么与workerGroup绑定的!!!!继续追踪doBind0方法.....
![12062369-cd97ea1270c7dc92.png](https://i-blog.csdnimg.cn/blog_migrate/356e3fd6c4a1e3690c34801965b54131.webp?x-image-process=image/format,png)
![12062369-9855f3c1d2fd1596.png](https://i-blog.csdnimg.cn/blog_migrate/97c41da04345d6611ea14ed64e505b06.webp?x-image-process=image/format,png)
![12062369-d9609561ea945c18.png](https://i-blog.csdnimg.cn/blog_migrate/ca5786c6333bf3208af54aeb016bd9c7.webp?x-image-process=image/format,png)
![12062369-c17959b4e5f646d0.png](https://i-blog.csdnimg.cn/blog_migrate/17bea7e2c74b1742041261e232fa3eec.webp?x-image-process=image/format,png)
![12062369-e055faa92f404a66.png](https://i-blog.csdnimg.cn/blog_migrate/b3e301e71f122cae16a03f0083d8c77c.webp?x-image-process=image/format,png)
doBind0中我们发现,刚刚初始化并且绑定了NioEventLoop的方法调用了eventLoop()方法得到了一个Boss的NioEventLoop,然后调用NioEventLoop的execute方法执行一个任务,execute方法是NioEventLoop的父类SingleThreadEventExecutor的方法,该方法内部调用了startThread方法、doStartThread方法,最终到executor.execute方法,而这里的executor就是我们在new NioEventLoopGroup的内部创建的那个ThreadPerTaskExecutor,该方法中调用了SingleThreadEventExecutor.this.run(),而这个run方法正是我们的NioEventLoop的run方法!!!
我们追踪run方法可以找到一条调用链processSelectedKeysPlain(selector.selectedKeys())-->processSelectedKeysPlain-->processSelectedKey-->
![12062369-ac18254a04ed9fff.png](https://i-blog.csdnimg.cn/blog_migrate/8e87c6019c11fd96eb602f743358d520.webp?x-image-process=image/format,png)
最终调用了8.2的方法,整个调用链都是由bossGroup中的线程执行的,知道这里unsafe.read()方法,该方法会继续调用链,一直到
![12062369-6046bd560c5663f1.png](https://i-blog.csdnimg.cn/blog_migrate/d15758f65fc1e00d9f95736fab216dfb.webp?x-image-process=image/format,png)
一直到doReadMessages方法,可以看到,该方法内部new NioSocketChannel里边设置了绑定了一个workerGroup的线程!!!回到doReadMessages上一层,我们这里给绑定一个worker线程之后,boss线程就会调用fireChannelRead方法,这时候真正执行的线程就是worker了!!!
我们再次审视下ThreadPerTaskExecutor这个类:
![12062369-ced21a49fec48db2.png](https://i-blog.csdnimg.cn/blog_migrate/14bca3d0b4780a343cd96cfcc8fd46d6.webp?x-image-process=image/format,png)
刚刚的execute方法就是这里的execute,这里先利用线程工厂新建了一个线程,然后调用了线程的start()方法,真正的启动了NioEventLoop这个线程的run方法!!!
OK,以上这个过程解释了NioEventLoop线程模型怎么干的问题!