新聞中心
本文轉(zhuǎn)載自微信公眾號(hào)「yes的練級(jí)攻略」,作者是Yes呀。轉(zhuǎn)載本文請(qǐng)聯(lián)系yes的練級(jí)攻略公眾號(hào)。

創(chuàng)新互聯(lián)是一家專注于成都做網(wǎng)站、網(wǎng)站建設(shè)和托管服務(wù)器的網(wǎng)絡(luò)公司,有著豐富的建站經(jīng)驗(yàn)和案例。
你好,我是yes。
開(kāi)年第一篇技術(shù)文哈,這是之前的存貨,最近一段時(shí)間的更新還是會(huì)以面試題為主,畢竟金三銀四哈。
這篇其實(shí)也算是一個(gè)面試點(diǎn),畢竟 Reactor 也是可能被問(wèn)到的,讓我們來(lái)看看 Reactor 具體是如何在 Netty 中落地實(shí)現(xiàn)的。
可以加深下對(duì) Reactor 的印象,還有 Reactor 模型的演進(jìn)過(guò)程。
對(duì)了,之前已經(jīng)寫(xiě)了一篇對(duì) Reactor 的理解,沒(méi)看過(guò)的建議先看那篇,然后再來(lái)看這篇。
話不多說(shuō),發(fā)車!
Netty 的 Reactor
我們都知道 Netty 可以有兩個(gè)線程組,一個(gè)是 bossGroup,一個(gè)是 workerGroup。
之前也提到了 bossGroup 主要是接待新連接(老板接活),workerGroup 是負(fù)責(zé)新連接后續(xù)的一切 I/O (員工干活)
對(duì)應(yīng)到 Reactor 模型中,bossGroup 中的 eventLoop 就是主 Reactor。它的任務(wù)就是監(jiān)聽(tīng)等待連接事件的到來(lái),即 OP_ACCEPT。
然后創(chuàng)建子 channel ,從 workerGroup 中選擇一個(gè) eventLoop ,將子 channel 與這個(gè) eventLoop 綁定,之后這個(gè)子 channel 對(duì)應(yīng)的 I/O事件,都由這個(gè) eventLoop 負(fù)責(zé)。
而這個(gè) workerGroup 中的 eventLoop 就是所謂的子 Reactor,它的任務(wù)就是負(fù)責(zé)已經(jīng)建連完畢的連接之后的所有 I/O 請(qǐng)求。
其實(shí)從 eventLoop 這個(gè)名字就能看出,它的作用就是 loop event,說(shuō)白了就是一個(gè)線程,死循環(huán)的等待事件的發(fā)生,然后根據(jù)不同的事件類型進(jìn)行不一樣的后續(xù)處理,僅此而已。
正常情況下 bossGroup 只會(huì)配置一個(gè) eventLoop,即一個(gè)線程,因?yàn)橐话惴?wù)只會(huì)暴露一個(gè)端口,所以只要一個(gè) eventLoop 監(jiān)聽(tīng)這個(gè)端口,然后 accept 連接。
而 workerGroup 在 Netty 中,默認(rèn)是 cpu 核心數(shù)*2,例如 4 核 CPU ,默認(rèn)會(huì)在 workerGroup 建 8 個(gè) eventLoop,所以就有 8 個(gè)子 Reactor。
所以正常 Netty 服務(wù)端的配置是,1個(gè)主 Reactor,多個(gè)從 Reactor,這就是所謂的主從 Reactor。
基本上現(xiàn)在的主流配置都是主從 Reactor。
關(guān)于 Reactor 模型的演進(jìn)
在深入 Netty 的 Reactor 實(shí)現(xiàn)之前,我們先來(lái)看看,為什么會(huì)演變成主從 Reactor?
最開(kāi)始的模型是單 Reactor 單線程 ,你可以理解成一個(gè)線程來(lái)即監(jiān)聽(tīng)新的連接,又要響應(yīng)老的連接的請(qǐng)求,如果邏輯處理的很快,那沒(méi)有問(wèn)題,看看人家 redis 就夠用,但是如果邏輯處理的慢,那就會(huì)阻塞其他請(qǐng)求。
所以就有了單 Reactor 多線程,還是由一個(gè)線程來(lái)監(jiān)聽(tīng)所有的底層 Socket,但是一些耗時(shí)的操作可以分配給線程池進(jìn)行業(yè)務(wù)處理,這樣就不會(huì)因?yàn)檫壿嬏幚砺龑?dǎo)致 Reactor 的阻塞。
但是這個(gè)模型還會(huì)有瓶頸,即監(jiān)聽(tīng)新的連接和響應(yīng)老的連接的請(qǐng)求都由一個(gè)線程處理,積累的老連接多了,有很多事件需要響應(yīng),就會(huì)影響新連接的接入,這就不太舒服了,況且我們現(xiàn)在都是多核 CPU,還差這么一個(gè)線程嗎?
所以就又演進(jìn)成主從 Reactor,由一個(gè)線程,即主 Reactor 專門(mén)等待新連接的建連,然后創(chuàng)建多個(gè)線程作為子 Reactor,均勻的負(fù)責(zé)已經(jīng)接入的老連接,這樣一來(lái)既不會(huì)影響接待新連接的速度,也能更好的利用多核 CPU 的能力響應(yīng)老連接的請(qǐng)求。
這就是關(guān)于 Reactor 模型的演進(jìn)了。
好了,接下來(lái)我們?cè)倏纯?Netty 實(shí)現(xiàn) Reactor 的核心類,我們現(xiàn)在一般都是用 NIO ,所以我們看 NioEventLoop 這個(gè)類。
友情提示,有條件建議在PC端看下面的內(nèi)容,源碼類的手機(jī)上看不太舒服
NioEventLoop
前面我們已經(jīng)提到一個(gè) NioEventLoop 就是一個(gè)線程,那線程的核心肯定就是它的 run 方法。
基于我們的理解,我們知道這個(gè) run 方法的主基調(diào)肯定是死循環(huán)等待 I/O 事件產(chǎn)生,然后處理事件。
事實(shí)也是如此, NioEventLoop 主要做了三件事:
- select 等待 I/O 事件的發(fā)生
- 處理發(fā)生的 I/O 事件
- 處理提交至線程中的任務(wù),包括提交的異步任務(wù)、定時(shí)任務(wù)、尾部任務(wù)。
首先折疊下代碼,可以看到妥妥的死循環(huán),這也是 Reactor 線程的標(biāo)配,這輩子無(wú)限只為了等待事件發(fā)生且處理事件。
在 Netty 的實(shí)現(xiàn)里,NioEventLoop 線程不僅要處理 I/O 事件,還需要處理提交的異步任務(wù)、定時(shí)任務(wù)和尾部任務(wù),所以這個(gè)線程需要平衡 I/O 事件處理和任務(wù)處理的時(shí)間。
因此有個(gè) selectStrategy 這樣的策略,根據(jù)判斷當(dāng)前是否有任務(wù)在等待被執(zhí)行,如果有則立即進(jìn)行一次不會(huì)阻塞的 select 來(lái)嘗試獲取 I/O 事件,如果沒(méi)任務(wù)則會(huì)選擇 SelectStrategy.SELECT 這個(gè)策略。
從圖中也可以看到,這個(gè)策略會(huì)根據(jù)最近將要發(fā)生的定時(shí)任務(wù)的執(zhí)行時(shí)間來(lái)控制 select 最長(zhǎng)阻塞的時(shí)間。
從下面的代碼可以看到,根據(jù)定時(shí)任務(wù)即將執(zhí)行的時(shí)間還預(yù)留了 5 微秒的時(shí)間窗口,如果 5 微秒內(nèi)就要到了,那就不阻塞了,直接進(jìn)行一個(gè)非阻塞的 select 立刻嘗試獲取 I/O 事件。
經(jīng)過(guò)上面的這個(gè)操作,select 算是完畢了,最終會(huì)把就緒的 I/O 事件個(gè)數(shù)賦值給 strategy,如果沒(méi)有的話那 strategy 就是 0 ,接著就該處理 I/O 事件和任務(wù)了。
上面代碼我把重點(diǎn)幾個(gè)部分都框出來(lái)了,這里有個(gè) selectCnt 來(lái)統(tǒng)計(jì) select 的次數(shù),這個(gè)用于處理 JDK Selector 空輪詢的 bug ,下面會(huì)提。
ioRatio 這個(gè)參數(shù)用來(lái)控制 I/O 事件執(zhí)行的時(shí)間和任務(wù)執(zhí)行時(shí)間的占比,畢竟一個(gè)線程要做多個(gè)事情,要做到雨露均沾對(duì)吧,不能冷落了誰(shuí)。
可以看到,具體的實(shí)現(xiàn)是記錄 I/O 事件的執(zhí)行時(shí)間,然后再根據(jù)比例算出任務(wù)能執(zhí)行的最長(zhǎng)的時(shí)間來(lái)控制任務(wù)的執(zhí)行。
I/O 事件的處理
我們來(lái)看看 I/O 事件具體是如何處理的,也就是 processSelectedKeys 方法。
點(diǎn)進(jìn)去可以看到,實(shí)際上會(huì)有兩種處理的方法,一種是優(yōu)化版,一種是普通版。
這兩個(gè)版本的邏輯都是一樣的,區(qū)別就在于優(yōu)化版會(huì)替換 selectedKeys 的類型,JDK 實(shí)現(xiàn)的 selectedKeys 是 set 類型,而 Netty 認(rèn)為這個(gè)類型的選擇還是有優(yōu)化的余地的。
Netty 用 SelectedSelectionKeySet 類型來(lái)替換了 set 類型,其實(shí)就是用數(shù)組來(lái)替換了 set
相比 set 類型而言,數(shù)組的遍歷更加高效,其次數(shù)組尾部添加的效率也高于 set,畢竟 set 還可能會(huì)有 hash沖突。當(dāng)然這是 Netty 為追求底層極致優(yōu)化所做的,我們平日的代碼沒(méi)必要這般“斤斤計(jì)較”,意義不大。
那 Netty 是通過(guò)什么辦法替換了這個(gè)類型呢?
反射。
看下代碼哈,不是很復(fù)雜:
這也能給我們提供一些思路,比方你調(diào)用三方提供的 jar 包,你無(wú)法修改它的源碼,但是你又想對(duì)它做一些增強(qiáng),那么就可以仿照 Netty 的做法,通過(guò)反射來(lái)替換之~
我們打個(gè)斷點(diǎn)看下替換前后 selectedKey 的類型,之前是 HashSet:
替換了后就變成了 SelectedSelectionKeySet 了。
ok,現(xiàn)在我們?cè)倏聪聝?yōu)化版的處理 I/O 事件的遍歷方法,和普通版邏輯一樣的,只是遍歷是利用數(shù)組罷了。
沒(méi)啥好說(shuō)的,就那個(gè)幫助 GC 可以提一下,如果你看過(guò)很多開(kāi)源軟件你就會(huì)發(fā)現(xiàn)有很多這樣的實(shí)現(xiàn),直接置為 null 的語(yǔ)句,這是為了幫助 GC。
緊接著看下真正處理 I/O 事件的方法 processSelectedKey
可以看到,這個(gè)方法本質(zhì)就是根據(jù)不同的事件進(jìn)行不同的處理,實(shí)際上會(huì)將事件在對(duì)應(yīng)的 channel 的 pipeline 上面?zhèn)鞑ィ⑶矣|發(fā)各種相應(yīng)的自定義事件,我拿 OP_ACCEPT 事件作為例子分析。
針對(duì) OP_ACCEPT 事件,unsafe.read 實(shí)際會(huì)調(diào)用 NioMessageUnsafe#read 方法。
從上面代碼來(lái)看,邏輯并不復(fù)雜,主要就是循環(huán)讀取新建立的子 channel,并觸發(fā) ChannelRead 和 ChannelReadComplete 事件,使之在 pipeline 中傳播,期間就會(huì)觸發(fā)之前添加的 ServerBootstrapAcceptor#channelRead,將其分配給 workerGroup 中的 eventLoop ,即子 Reactor 線程。
當(dāng)然,我們自定義的 handler 也可以實(shí)現(xiàn)這兩個(gè)事件方法,這樣對(duì)應(yīng)的事件到來(lái)后,我們能進(jìn)行相應(yīng)的邏輯處理。
好了,Netty 的 OP_ACCEPT 事件處理分析到此結(jié)束,其他事件也是類似的,都會(huì)觸發(fā)相應(yīng)的事件,然后在 pipeline 中傳遞,觸發(fā)不同 Channelhandler 的方法,進(jìn)行邏輯處理。
以上,就是 Netty 實(shí)現(xiàn)的主從 Reactor 模型。
當(dāng)然,Netty 也支持單 Reactor,無(wú)非就是不要 workerGroup,至于線程數(shù)也可以自行配置,十分靈活,不過(guò)現(xiàn)在一般用的都是主從 Reactor 模型。
最后
這篇不僅講了 Netty 的 Reactor 實(shí)現(xiàn),也把 Netty 是如何處理 I/O 操作的部分也囊括了。
下篇關(guān)于 Netty 的再盤(pán)盤(pán) pipeline 機(jī)制,這個(gè)責(zé)任鏈模式也是很重要的,很有啟發(fā)性。
等 pipeline 寫(xiě)完之后,你對(duì) Netty 整體應(yīng)該有一個(gè)比較清晰的認(rèn)識(shí)了,然后會(huì)開(kāi)始寫(xiě)一些粘包半包、內(nèi)存管理等內(nèi)容,包括一些 Netty 的“高級(jí)”用法啥的,總之大概還有一半的內(nèi)容沒(méi)寫(xiě),等寫(xiě)完之后,完整的回顧一遍,出去可以拿 Netty “吹”了。
好嘞,等我更新哈,不多BB了。
當(dāng)前文章:老面試官竟問(wèn)我Reactor在Netty中是如何實(shí)現(xiàn)的
網(wǎng)頁(yè)URL:http://fisionsoft.com.cn/article/cojdhee.html


咨詢
建站咨詢
