Netty5.0工作原理

前言:

前面章节我们对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原理分析:

NioEventLoop类图

由该类图可以看出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线程组,追踪其源码调用链

1
2
3
4
5
6

好,追踪到这里我们可以发现,在不填写参数的情况下创建的线程数是CPU个数的2倍!继续往下追踪.....

7
7.1

调用链到这里只有nThreads是有值的,executor为空,所以新建了一个ThreadPerTaskExecutor,这个类本质上是一个Java的Executor线程执行器,可以执行任务,继续。。。。

7.2

还是7环节中的构造方法,我们看到,这个MultiThreadEventLoopGroup内置了一个线程执行器数组,数组长度与线程个数相同,下面调用了newChild给每个执行器数组元素赋值,点击进入newChild方法,由于我们调用的是NioEventLoopGroup,所以进入NioEventLoop的实现。。。。

7.3
7.3.1
7.3.2

看见没,这个new NioEventLoop(this,executor,(SelectorProvider)args[0]);方法,这就是最终NioEventLoopGroup线程组的组成元素,构造NioEventLoop过程中给它传入了我们的ThreadPerTaskExecutor执行器。最后这个调用链还调用了openSelector()启动Selector!!!

总结:压缩以上过程,NioEventLoopGroup通过内置EventExecutor数组而实现线程组,而EventExecutor数组内部元素是NioEventLoop,所以可以说NioEventLoopGroup在构造过程中创建了2*CPU个NioEventLoop待用!!!!

4:启动:

前面我们剖析了NioEventLoopGroup是怎么由NioEventLoop构成的,下面我们分析下NioEventLoop是怎么工作的!

1

我们直接看图1,bootstrap.bind方法,点击进入调用链

2

绑定IP和端口。。。

3
4

调用至此,首先initAndRegister方法初始化了一条通道,并把ChannelFuture预期也绑定到了通道上。...

4.1

可以看到initAndRegister创建了一条通道并对通道进行了初始化,这个过程实际上把Channel通道内部的eventLoop设置成了bossGroup中的NioEventLoop

4.1.1

这是ServerBootstrap中的方法,group()方法返回的实际上是bossGroup,next()方法则从bossGroup中返回一个NioEventLoop。

直到目前,所有的工作还都是有main线程来执行的,channelFactory方法返回的是ServerBootstrapChannelFactory,newChannel方法创建的是NioServerSocketChannel(还记得你写的代码里.channel(NioServerSocketChannel)方法吗?这里的channel就是根据这个来的,具体源码略)

4.1.2
4.1.3

NioServerSocketChannel实例化的时候设置了感兴趣的通道事件为OP_ACCEPT!!!沿着这个实例化链,一直到AbstractChannel,最终给该实例设置了pipeline、unsafe、bossGroup的NioEventLoop三个成员变量。我们继续查看4.1环节中的channel.unsafe.register()方法,我们现在知道channel是我们刚刚创建的NioServerSocketChannel实例,unsafe方法返回的是该实例的成员变量,该实例内部还有一个bossGroup的线程模型实例。

4.1.4

这个register方法是AbstractChannel中的方法,所以eventLoop就是boss线程模型,boss线程模型执行register0方法,所以这个register方法绑定了通道与bossgroup线程!!!继续看init(channel)方法、、、

4.1.5

main线程执行的init方法是ServerBootstrap中的方法,其中handler()方法就是我们代码里写的.handler(handler)。init方法内部还创建了一条流水线pipeline,并在流水线里加入了一个ServerBootstrapAcceptor,注意,我们用的是netty5.0,这和netty4.0里是有变化的,netty5.0并没有直接给ServerBootstrapAcceptor设置一个workerGroup,而仅仅是设置了一些childHandler!!!

我们继续追踪,希望找到通道是怎么与workerGroup绑定的!!!!继续追踪doBind0方法.....

5
6
7
8
8.1

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-->

8.2

最终调用了8.2的方法,整个调用链都是由bossGroup中的线程执行的,知道这里unsafe.read()方法,该方法会继续调用链,一直到

8.3

一直到doReadMessages方法,可以看到,该方法内部new NioSocketChannel里边设置了绑定了一个workerGroup的线程!!!回到doReadMessages上一层,我们这里给绑定一个worker线程之后,boss线程就会调用fireChannelRead方法,这时候真正执行的线程就是worker了!!!


我们再次审视下ThreadPerTaskExecutor这个类:

9

刚刚的execute方法就是这里的execute,这里先利用线程工厂新建了一个线程,然后调用了线程的start()方法,真正的启动了NioEventLoop这个线程的run方法!!!

OK,以上这个过程解释了NioEventLoop线程模型怎么干的问题!

总结:追踪源码是个很令人头疼的问题,由于都是面向接口编程,所以我们观察的时候要十分注意不要看错了方法,有的方法是父类的方法,有的方法是子类本身的方法!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容