前言:
前面章節(jié)我們對(duì)Netty的整體結(jié)構(gòu)和使用流程進(jìn)行了剖析遭贸,使用過(guò)程中我們首先創(chuàng)建了兩個(gè)線程組EventLoopGroup,一個(gè)負(fù)責(zé)連接分派心软,一個(gè)負(fù)責(zé)IO讀寫(xiě)壕吹,那么這兩個(gè)線程組工作原理是怎么樣的呢?由于NioEventLoopGroup應(yīng)用較為廣泛删铃,我們從這個(gè)線程組開(kāi)刀耳贬!
結(jié)論:
由于文章源碼冗長(zhǎng),如果你沒(méi)興趣看(我猜測(cè)你應(yīng)該沒(méi)興趣)猎唁,直接看結(jié)論咒劲。
一:NioEventLoopGroup構(gòu)建的兩個(gè)線程組boss和worker,線程個(gè)數(shù)默認(rèn)為CPU個(gè)數(shù)的2倍诫隅。
二:NioEventLoopGroup中的(boss和worker)線程底層通過(guò)Java NIO中的Selector實(shí)現(xiàn)對(duì)通道的綁定和監(jiān)聽(tīng)腐魂。
三:boss線程執(zhí)行通道連接,當(dāng)監(jiān)聽(tīng)到read事件之后綁定worker線程和通道逐纬。worker線程執(zhí)行通道的IO讀寫(xiě)蛔屹。
四:netty本質(zhì)上還是NIO而不是AIO,只不過(guò)執(zhí)行的線程我們可以指定豁生。
1:Reactor模型:
Reactor模型分為三種兔毒,根據(jù)并發(fā)的用戶量性能由低到高
第一種是單線程接收多客戶端連接請(qǐng)求并親自處理IO讀寫(xiě)
第二種單線程接收多客戶端連接請(qǐng)求請(qǐng)求业筏,轉(zhuǎn)發(fā)給其他線程池進(jìn)行IO讀寫(xiě)
第三種多線程接收多客戶端連接請(qǐng)求吧雹,轉(zhuǎn)發(fā)給其他線程池進(jìn)行IO讀寫(xiě)
Netty可以完全吸納該模型,server端可以通過(guò)設(shè)置bossgroup和workergroup來(lái)選擇使用第二種和第三種模型愁溜。
2:NioEventLoop線程模型:
我們用到的線程組EventLoopGroup workerGroup = new NioEventLoopGroup()實(shí)際上是有每個(gè)NioEventLoop線程模型組成的芍殖。
類似于web天生的多線程豪嗽,只要每個(gè)業(yè)務(wù)邏輯的Handler(車間)是無(wú)狀態(tài)的,那么所有的NioEventLoop(工人)就可以并發(fā)執(zhí)行豌骏,而不需要加鎖龟梦。
A:NioEventLoop原理分析:
由該類圖可以看出NioEventLoop線程模型頂層接口實(shí)現(xiàn)了ScheduledExecutorService,這是Java concurrent包里面的接口肯适,該接口可以執(zhí)行定時(shí)任務(wù)变秦,所以毫無(wú)疑問(wèn),NioEventLoop模型也能執(zhí)行任務(wù)并且能執(zhí)行定時(shí)任務(wù)框舔!
打開(kāi)源碼蹦玫,我們發(fā)現(xiàn)NioEventLoop內(nèi)部依賴了一個(gè)Selector,這個(gè)Selector是Java NIO中的Selector刘绣,所以可見(jiàn)Netty本質(zhì)上并不是AIO而是NIO樱溉。
那么,NioEventLoop到底能干哪些事呢纬凤?
a:打開(kāi)了Selector福贞,run方法中,根據(jù)是否有要執(zhí)行的任務(wù)來(lái)選擇調(diào)用selectNow方法和select方法(該方法沒(méi)有會(huì)輪詢)停士,值得深思的是一個(gè)線程模型打開(kāi)一個(gè)Selector挖帘。
b:注冊(cè)通道完丽,注冊(cè)通道方法register,通過(guò)該方法綁定通道和線程拇舀。
c:執(zhí)行任務(wù)逻族,run方法最終調(diào)用的processSelectedKey方法是真正執(zhí)行任務(wù)的代碼,該方法通過(guò)Java NIO中的SelectionKey和channel真正執(zhí)行執(zhí)行通道的IO讀寫(xiě)操作骄崩。
以上過(guò)程是不是有種似曾相識(shí)的感覺(jué)聘鳞?沒(méi)錯(cuò),就是Java NIO的操作流程的封裝要拂!
3:NioEventLoopGroup原理分析:
我們前面說(shuō)了NioEventLoop能干什么抠璃,但是并沒(méi)有說(shuō)怎么干的,誰(shuí)指使他干的脱惰?我們下面回答這個(gè)問(wèn)題搏嗡。
首先,我們新建NioEventLoopGroup線程組枪芒,追蹤其源碼調(diào)用鏈
好彻况,追蹤到這里我們可以發(fā)現(xiàn),在不填寫(xiě)參數(shù)的情況下創(chuàng)建的線程數(shù)是CPU個(gè)數(shù)的2倍舅踪!繼續(xù)往下追蹤.....
調(diào)用鏈到這里只有nThreads是有值的纽甘,executor為空,所以新建了一個(gè)ThreadPerTaskExecutor抽碌,這個(gè)類本質(zhì)上是一個(gè)Java的Executor線程執(zhí)行器悍赢,可以執(zhí)行任務(wù),繼續(xù)货徙。左权。。痴颊。
還是7環(huán)節(jié)中的構(gòu)造方法赏迟,我們看到,這個(gè)MultiThreadEventLoopGroup內(nèi)置了一個(gè)線程執(zhí)行器數(shù)組蠢棱,數(shù)組長(zhǎng)度與線程個(gè)數(shù)相同锌杀,下面調(diào)用了newChild給每個(gè)執(zhí)行器數(shù)組元素賦值,點(diǎn)擊進(jìn)入newChild方法泻仙,由于我們調(diào)用的是NioEventLoopGroup糕再,所以進(jìn)入NioEventLoop的實(shí)現(xiàn)。玉转。突想。。
看見(jiàn)沒(méi),這個(gè)new NioEventLoop(this,executor,(SelectorProvider)args[0])猾担;方法袭灯,這就是最終NioEventLoopGroup線程組的組成元素,構(gòu)造NioEventLoop過(guò)程中給它傳入了我們的ThreadPerTaskExecutor執(zhí)行器绑嘹。最后這個(gè)調(diào)用鏈還調(diào)用了openSelector()啟動(dòng)Selector<寺!圾叼!
總結(jié):壓縮以上過(guò)程,NioEventLoopGroup通過(guò)內(nèi)置EventExecutor數(shù)組而實(shí)現(xiàn)線程組捺癞,而EventExecutor數(shù)組內(nèi)部元素是NioEventLoop夷蚊,所以可以說(shuō)NioEventLoopGroup在構(gòu)造過(guò)程中創(chuàng)建了2*CPU個(gè)NioEventLoop待用!K杞椤L韫摹!
4:?jiǎn)?dòng):
前面我們剖析了NioEventLoopGroup是怎么由NioEventLoop構(gòu)成的唐础,下面我們分析下NioEventLoop是怎么工作的箱歧!
我們直接看圖1,bootstrap.bind方法一膨,點(diǎn)擊進(jìn)入調(diào)用鏈
綁定IP和端口呀邢。。豹绪。
調(diào)用至此价淌,首先initAndRegister方法初始化了一條通道,并把ChannelFuture預(yù)期也綁定到了通道上瞒津。...
可以看到initAndRegister創(chuàng)建了一條通道并對(duì)通道進(jìn)行了初始化蝉衣,這個(gè)過(guò)程實(shí)際上把Channel通道內(nèi)部的eventLoop設(shè)置成了bossGroup中的NioEventLoop
這是ServerBootstrap中的方法,group()方法返回的實(shí)際上是bossGroup巷蚪,next()方法則從bossGroup中返回一個(gè)NioEventLoop病毡。
直到目前,所有的工作還都是有main線程來(lái)執(zhí)行的屁柏,channelFactory方法返回的是ServerBootstrapChannelFactory啦膜,newChannel方法創(chuàng)建的是NioServerSocketChannel(還記得你寫(xiě)的代碼里.channel(NioServerSocketChannel)方法嗎?這里的channel就是根據(jù)這個(gè)來(lái)的前联,具體源碼略)
NioServerSocketChannel實(shí)例化的時(shí)候設(shè)置了感興趣的通道事件為OP_ACCEPT9ζ荨!似嗤!沿著這個(gè)實(shí)例化鏈啸臀,一直到AbstractChannel,最終給該實(shí)例設(shè)置了pipeline、unsafe乘粒、bossGroup的NioEventLoop三個(gè)成員變量豌注。我們繼續(xù)查看4.1環(huán)節(jié)中的channel.unsafe.register()方法,我們現(xiàn)在知道channel是我們剛剛創(chuàng)建的NioServerSocketChannel實(shí)例灯萍,unsafe方法返回的是該實(shí)例的成員變量轧铁,該實(shí)例內(nèi)部還有一個(gè)bossGroup的線程模型實(shí)例。
這個(gè)register方法是AbstractChannel中的方法旦棉,所以eventLoop就是boss線程模型齿风,boss線程模型執(zhí)行register0方法,所以這個(gè)register方法綁定了通道與bossgroup線程0舐濉>劝摺!繼續(xù)看init(channel)方法真屯、脸候、、
main線程執(zhí)行的init方法是ServerBootstrap中的方法绑蔫,其中handler()方法就是我們代碼里寫(xiě)的.handler(handler)运沦。init方法內(nèi)部還創(chuàng)建了一條流水線pipeline,并在流水線里加入了一個(gè)ServerBootstrapAcceptor配深,注意携添,我們用的是netty5.0,這和netty4.0里是有變化的凉馆,netty5.0并沒(méi)有直接給ServerBootstrapAcceptor設(shè)置一個(gè)workerGroup薪寓,而僅僅是設(shè)置了一些childHandler!@焦病向叉!
我們繼續(xù)追蹤,希望找到通道是怎么與workerGroup綁定的`露D富选!京革!繼續(xù)追蹤doBind0方法.....
doBind0中我們發(fā)現(xiàn)奇唤,剛剛初始化并且綁定了NioEventLoop的方法調(diào)用了eventLoop()方法得到了一個(gè)Boss的NioEventLoop,然后調(diào)用NioEventLoop的execute方法執(zhí)行一個(gè)任務(wù)匹摇,execute方法是NioEventLoop的父類SingleThreadEventExecutor的方法咬扇,該方法內(nèi)部調(diào)用了startThread方法、doStartThread方法廊勃,最終到executor.execute方法懈贺,而這里的executor就是我們?cè)趎ew NioEventLoopGroup的內(nèi)部創(chuàng)建的那個(gè)ThreadPerTaskExecutor经窖,該方法中調(diào)用了SingleThreadEventExecutor.this.run(),而這個(gè)run方法正是我們的NioEventLoop的run方法K蟛印;隆!
我們追蹤run方法可以找到一條調(diào)用鏈processSelectedKeysPlain(selector.selectedKeys())-->processSelectedKeysPlain-->processSelectedKey-->
最終調(diào)用了8.2的方法堡妒,整個(gè)調(diào)用鏈都是由bossGroup中的線程執(zhí)行的配乱,知道這里unsafe.read()方法,該方法會(huì)繼續(xù)調(diào)用鏈皮迟,一直到
一直到doReadMessages方法搬泥,可以看到,該方法內(nèi)部new NioSocketChannel里邊設(shè)置了綁定了一個(gè)workerGroup的線程7帷S蛹亍!回到doReadMessages上一層烦粒,我們這里給綁定一個(gè)worker線程之后,boss線程就會(huì)調(diào)用fireChannelRead方法代赁,這時(shí)候真正執(zhí)行的線程就是worker了H潘!芭碍!
我們?cè)俅螌徱曄耇hreadPerTaskExecutor這個(gè)類:
剛剛的execute方法就是這里的execute徒役,這里先利用線程工廠新建了一個(gè)線程,然后調(diào)用了線程的start()方法窖壕,真正的啟動(dòng)了NioEventLoop這個(gè)線程的run方法S俏稹!瞻讽!
OK鸳吸,以上這個(gè)過(guò)程解釋了NioEventLoop線程模型怎么干的問(wèn)題!