vlambda博客
学习文章列表

谈谈你对Netty服务端的两个线程池的理解?



目录

服务器端初始化设置了两个EventLoopGroup,这两个EventLoopGroup是干什么用的?

缕清一些可能的认知误区——服务端的接入线程池到底启动了多少个I/O线程?

本篇文章大概1800字,阅读时间大约8分钟


本篇文章是对前面Netty服务端初始化过程相关文章的一个重新总结,并修改了一些错误,还是希望用面试题的形式来全面总结Netty的设计思想和使用的细节,本文主要是重点review了Netty的两个线程池的关系和作用,不妨带着目录中的问题进行阅读。即:


1、服务器端初始化设置了两个EventLoopGroup,这两个EventLoopGroup是干什么用的?

2、缕清一些可能的认知误区——服务端的接入线程池到底启动了多少个I/O线程?

谈谈你对Netty服务端的两个线程池的理解?

理解了这两个线程池的作用和原理,就能理解所谓的高性能服务器模型——reactor模型,也叫反应堆(器)模式。下面带着问题去深入。补充参考:



下面我先说第二个问题:缕清一些可能的认知误区——服务端的接入线程池到底启动了多少个I/O线程?


如下Netty服务端demo配置线程池的代码,我将新客户端连接的接入线程池命名为了bossGroup,也有人叫它boss线程池,或者accetp线程池。。。第二个线程池我命名为了workerGroup,这都是个人/团队喜好,无所谓怎么命名,知道怎么回事儿就行

谈谈你对Netty服务端的两个线程池的理解?

注意的地方:我将bossGroup的大小设置为1,是因为Netty的所谓多线程reactor模型,其接入线程池是一个端口对应一个NIO线程,即reactor线程,实际上TCP协议的端口也确实只支持一个线程监听,如果想让bossGroup实际启动多个NIO线程去接收新客户端连接,那么可以监听多个服务端的端口来实现。


也就是说,我的demo将boss线程池大小主动设置为1,算是一种最佳实践,参考:


如果较真儿的话,那么“最佳”这个词有一些类似自媒体的夸张手法了,为什么呢,看下面的解释:

1、然boss线程池配置多个线程数,或者沿用Netty默认设置(2倍CPU核数个),但它实际运行时只会启动一个

2、Netty服务端绑定一个端口的背景下,把boss的线程数设置为多个或者默认配置(默认2倍CPU核数个),是有一些浪费内存的,我猜想Netty这样设计,也是为了代码封装和复用,否则Netty线程池的构造器就不好设计了,因为boss线程池的代码,worker线程池也会复用。


所以综合来看,boss线程池即使配置了多个线程数,但是只启动一个,占用资源是很少的,不配置为1也不能说不是最佳,但是,我个人的倾向是主动配置为1,追求极致。


而且如果有心去看一些依赖了Netty的开源框架或者公司内部的一些网络组件,那么你会发现有人就直接将boss线程池的大小设置为了1,这说明写代码的人肯定看过Netty源码,也能从侧面反映这样的组件往往比较靠谱,因为只有对所依赖的组件了如指掌,这样写出来的中间件或者底层库才能避免踩坑,这是一个侧面的反应吧,具体还得结合其它点评价。


有些跑题,看第一个问题:服务器端初始化设置了两个EventLoopGroup,这两个EventLoopGroup是干什么用的?


先知道一个前提,在Netty里,所谓的EventLoopGroup(NioEventLoopGroup)本质就是线程池,如下是它的类图:

谈谈你对Netty服务端的两个线程池的理解?

EventLoopGroup继承并扩展了JDK的线程池实现——ExecutorService类,从命名也能知道,Group代表线程的组,而Netty里的XxxEventLoop类(一般常用就是NioEventLoop)是和线程对应的,即一个EventLoop将由一个永远都不会变的Java的Thread驱动,或者说一个EventLoop绑定(代码实现就是聚合)了一个线程(也叫它I/O线程),这个被绑定的线程的底层是JDK的Thread类。


综上,知道bossGroup和workerGroup是两个Netty的线程池,且复用了一套代码。


形象的解释,可以把Netty比作一个饭店,bossGroup就像一个前台接待,比喻不太恰当,叫老板比较合适,但是老板一般也不去前台接待。。。意思理解到位即可。当客户来到饭店,接待就会引导顾客就坐,为顾客端茶送水,负责点餐等,而workerGroup就是实际上做饭的厨师。


回到Netty,bossGroup用于服务端Channel接收(术语也叫accept)新的客户端连接,而workerGroup负责客户端连接上的I/O操作,当接待员——bossGroup招待好顾客后就可以稍做休息或者等待下一个顾客到来,此时后厨里的厨师——workerGroup就开始忙碌地准备饭菜。


如上比喻可能不恰当,但是大概是这么个意思。


关于bossGroup与workerGroup的关系,可用下图来展示(图片来自网

络,侵删,我觉得这个图画的很好了,没必要重画):

谈谈你对Netty服务端的两个线程池的理解?

首先,服务端的bossGroup不断地监听是否有新客户端Channel到来,当发现有一个新的客户端Channel到来时,bossGroup就会为此Channel初始化各项资源。


然后,从workerGroup中挑选出一个EventLoop(就是demo里的NioEventLoop,它和Java的线程一一对应),并绑定到此新客户端Channel上。


接下来,服务端与客户端的通信过程就全部在workerGroup里分配的NioEventLoop线程中进行。显然,可知bossGroup会和NioServerSocketChannel关联,workerGroup必然会和NioSocketChannel关联。这两个是Netty封装的Channel,不同于JDK的Channel。


回忆前面Netty服务端init()方法中,初始化NioServerSocketChannel时会配置服务端的大动脉pipeline——即业务逻辑的抽象链,它为pipeline中添加了一个ChannelInitializer,在这个ChannelInitializer中启动了一个线程,异步的默认为服务端Channel的pipeline添加一了个ServerBootstrapAcceptor——新连接接入器,后续该接入器负责的就是新连接接入的逻辑,这是服务端Channel初始化过程中最重要的一步。


多说一句:其实在新连接接入器ServerBootstrapAcceptor里就将workerGroup关联到了客户端的NioSocketChannel,具体细节参考:



END


点亮在看,你最好看

~

阅读原文,获得更多精彩内容