【原創(chuàng)】Netty 內(nèi)核

作者:星巴刻

? ? ? ?

一、Netty 內(nèi)核組

? ? ? ? Netty 運(yùn)行時(shí)包含了多個(gè)內(nèi)核。在服務(wù)端程序中,需要分別創(chuàng)建 parent 和 child 兩種內(nèi)核: 1 個(gè) parent 內(nèi)核和 16 個(gè) child 內(nèi)核( 8 核 CPU系統(tǒng)下的默認(rèn)數(shù))。為簡(jiǎn)便起見(jiàn),以下簡(jiǎn)單以 16 來(lái)代替實(shí)際的 child 內(nèi)核數(shù)。因此,用如下大圖來(lái)概括 Netty 內(nèi)核組:中間是 17 個(gè)內(nèi)核組成的內(nèi)核組,左邊是操作系統(tǒng),右邊是應(yīng)用程序:

Netty 內(nèi)核組

? ? ? ? 每一個(gè) Channel 都必須屬于且僅屬于某一個(gè)內(nèi)核。系統(tǒng)中代表服務(wù)端偵聽(tīng)端口的 NioServerSocketChannel 屬于 parent 。代表不同客戶端連接的 NioSocketChannel 屬于 16 個(gè)child 中的一個(gè)。當(dāng) Channel 創(chuàng)建后,Netty 需要安排一個(gè)內(nèi)核來(lái)負(fù)責(zé)它,這個(gè)過(guò)程稱為 register (注冊(cè))。注冊(cè)過(guò)程就是調(diào)用 NioEventLoopGroup.next() 方法返回一個(gè) NioEventLoop,調(diào)用 NioEventLoop 的 register(channel)? 方法完成。

二、端口偵聽(tīng)內(nèi)核

端口偵聽(tīng)內(nèi)核圖

1、初始化

? ? ? ? 端口偵聽(tīng)內(nèi)核的創(chuàng)建與初始化屬于服務(wù)端整體初始化的一部分。這一部分可通過(guò) ServerBootstrap 完成。對(duì)照上圖,可以清晰地看出,初始化工作主要要構(gòu)建如下對(duì)象,并把它們「串在一起」:

1. 創(chuàng)建一個(gè) NioServerSocketChannel? ,用于表示用于接收接受客戶端連接的端口
2. 打開(kāi)一個(gè) Selector,用于發(fā)現(xiàn) NioServerSocketChannel 上有新的客戶端連接
3. 創(chuàng)建一個(gè) NioEventLoop 線程以及內(nèi)部隊(duì)列,讓執(zhí)行端口綁定、客戶端連接到來(lái)等程序在這個(gè)線程執(zhí)行
4. 創(chuàng)建一個(gè)? ServerBootstrapAcceptor? ,接受新來(lái)的客戶端連接,并初始化后續(xù)處理它的對(duì)象。

? ? ? ? 應(yīng)用程序創(chuàng)建 NioEventLoopGroup 時(shí),NioEventLoopGroup 內(nèi)部將根據(jù)指定的參數(shù)自動(dòng)創(chuàng)建 NioEventLoop 實(shí)例。NioEventLoop 隨之把其內(nèi)部線程、隊(duì)列創(chuàng)建起來(lái),并把打開(kāi)一個(gè)新的 Selector 選擇器。這樣,內(nèi)核線程、內(nèi)部隊(duì)列、Selector 選擇器就天然地屬于這個(gè) NioEventLoop 了。

? ? ? ? 應(yīng)用程序調(diào)用 ServerBootstrap.bind() 方法時(shí),ServerBootstrap 將創(chuàng)建 NioServerSocketChannel 對(duì)象及其? ServerBootstrapAcceptor 實(shí)例放入 NioServerSocketChannel 的 pipeline 中去。隨后將創(chuàng)建的 NioServerSocketChannel 注冊(cè)到 NioEventLoop 中,由 NioEventLoop 在其內(nèi)部線程中執(zhí)行將 NioServerSocketChannel 注冊(cè)到 Selector 選擇器的代碼:

NioServerSocketChannel 與 Selector 的關(guān)聯(lián)

? ? ? ? 至此,端口偵聽(tīng)內(nèi)核的所有對(duì)象都創(chuàng)建完畢,內(nèi)部對(duì)象已經(jīng)關(guān)聯(lián)起來(lái)只差把內(nèi)核綁定到操作系統(tǒng)中。

2、注冊(cè) OP_ACCEPT

? ? ? ? 偵聽(tīng)內(nèi)核為了能夠感知有新的客戶端到來(lái),必須注冊(cè)對(duì) OP_ACCEPT 事件的興趣,這個(gè)工作在上面的初始化中完成,這里單獨(dú)列出來(lái)說(shuō)明。在 NioServerSocketChannel 注冊(cè)到內(nèi)核工作完成后, DefaultPipeline.channelActive 方法除了通知 channel 已經(jīng)打開(kāi),緊接著馬上調(diào)用 channel.read() ,在 Netty 中,channel.read 不是真正要去從系統(tǒng)緩沖區(qū)讀取信息,而是表示要注冊(cè)一個(gè)讀取事件。因此,channel.read() 的調(diào)用通過(guò) pipeline 后,最終將調(diào)用到 channel 自身的 doBeginRead() 方法,將? selectionKey 的 interestOps 屬性增加 OP_ACCEPT 值。相關(guān)源代碼如下:

DefaultChannelPipeline.HeadContext 主動(dòng)調(diào)用 channel.read()
注冊(cè) OP_ACCEPT

3、端口綁定

? ? ? ? 應(yīng)用程序調(diào)用 ServerBootstrap.bind() 完成相關(guān)的初始化工作后,最后就是將整個(gè)內(nèi)核和操作系統(tǒng)關(guān)聯(lián)起來(lái),也就是真正將 NioServerSocketChannel 綁定到指定的端口上。類(lèi)似 register,將 NioServerSocketChannel bind 到操作系統(tǒng)上,需要調(diào)用 Java Nio 的 ServerSocketChannel 的 bind 方法,這個(gè)工作在 Netty 內(nèi)核下,也將在 NioEventLoop 內(nèi)部線程來(lái)實(shí)際執(zhí)行:

在這里,將 bind 工作轉(zhuǎn)交給 NioEventLoop 的內(nèi)部線程
調(diào)用 Java Nio 的類(lèi)完成端口綁定

? ? ? ? 至此,Netty 已經(jīng)可以接收客戶端連接了。

4、接受連接

? ? ? ? 對(duì)照《端口偵聽(tīng)內(nèi)核圖》,當(dāng)有新的客戶端連接到來(lái)時(shí),NioEventLoop 調(diào)用選擇器選擇當(dāng)前發(fā)生的 I/O 事件時(shí),將得到含有 OP_ACCEPT 事件的 selectionKey。NioEventLoop 的 processSelectedKey 方法一一處理這些 I/O 事件,對(duì)于 OP_ACCEPT 事件, NioServerSocketChannel 的 doReadMessages 方法將封裝出一個(gè) NioSocketChannel:

? ? ? ? 這個(gè) NioSocketChannel 對(duì)象將被之前初始化時(shí)創(chuàng)建到 pipeline 中的 ServerBootstrapAcceptor 獲得,在里面將新的客戶端連接安排到某個(gè) child 內(nèi)核實(shí)例中:

? ? ? ? 至此,就可以進(jìn)行客戶端連接的讀寫(xiě)了。

三、連接的讀寫(xiě)

? ? ? ? 客戶端和服務(wù)端之間連接上的信息讀寫(xiě)以及處理,在 Netty 中使用如下統(tǒng)一的內(nèi)核來(lái)完成。在服務(wù)端程序中,由于多了一個(gè)偵聽(tīng)端口的組,此內(nèi)核在服務(wù)端中歸為 child 組;但在客戶端中,就只有這個(gè)組,此時(shí)它歸為客戶端中的 parent 組。這樣的分組稍微拗口,我們完全可以簡(jiǎn)單地直接稱為它「端口讀寫(xiě)內(nèi)核」,以區(qū)別服務(wù)端程序特有的「端口偵聽(tīng)內(nèi)核」。

端口讀寫(xiě)內(nèi)核圖

? ? ? ? 如前所述,一般地,服務(wù)端程序中會(huì)有 16 個(gè)連接讀寫(xiě)內(nèi)核,典型的客戶端通常只有 1 個(gè)。這主要是因?yàn)?,客戶端往往只和服?wù)端建立 1 個(gè)或少數(shù)幾個(gè)連接,而服務(wù)端則要同時(shí)維護(hù)數(shù)量龐大的客戶端連接。好在,1 個(gè)或多個(gè),對(duì) Netty 來(lái)說(shuō)其內(nèi)核架構(gòu)是統(tǒng)一的,我們可以統(tǒng)一來(lái)理解,不用分開(kāi)看。

? ? ? ? 端口讀寫(xiě)內(nèi)核中,一個(gè)內(nèi)核負(fù)責(zé)多個(gè) NioSocketChannel 連接,這些連接注冊(cè)到選擇器中,以便通過(guò)選擇器發(fā)現(xiàn)該 channel 的 I/O事件,其中 OP_READ 是最關(guān)鍵的 I/O 事件。NioEventLoop 的內(nèi)部線程調(diào)用選擇器進(jìn)行選擇,當(dāng)注冊(cè)到選擇器中的 NioSocketChannel 有新的 OP_READ 等 I/O 事件時(shí),完成底層操作后(比如將信息讀入 ByteBuf),NioEventLoop 將調(diào)用和該 channel 一一對(duì)應(yīng)的 ChannelPipeline 中的 ChannelInboundHandler 的 channelRead 等方法進(jìn)行處理,最終使得最右邊的應(yīng)用程序邏輯得到執(zhí)行。

? ? ? ? 每個(gè) NioSocketChannel 都有自己的 ChannelPipeline 對(duì)象。對(duì)照上面的內(nèi)核圖中 pipeline 的部分,左邊是它的 head,右邊是它的 tail。每個(gè) ChannelPipeline 可以簡(jiǎn)單地看做有 2 行,上面行是處理來(lái)自內(nèi)核發(fā)出的事件(簡(jiǎn)稱處理 InboundEvent ),底下行處理來(lái)自應(yīng)用程序發(fā)出的動(dòng)作(簡(jiǎn)稱處理 OutboundEvent )。每一行都可以包含不限制個(gè)數(shù)的 ChannelHandler 模塊。

? ? ? Netty 內(nèi)核是在其內(nèi)核線程中調(diào)用 ChannelPipleline 的方法提交處理 InboundEvent 或 OutboundEvent,但并不意味著 ChannelPipeline 中的 ChannelHandler 的 channelRead 等方法一定是在 Netty 的內(nèi)核線程中執(zhí)行的。這主要 bootstrap 中,ChannelInitializer.initChannel 方法中是如何調(diào)用 pipeline 的,以調(diào)用 addLast 為例子,如果調(diào)用的是 addLast(EventExecutorGroup, ChannelHandler...handlers),即在第 1 個(gè)參數(shù)指定了一個(gè) EventExecutorGroup,那么 handlers 中的方法將由這個(gè) EventExecutorGroup 提供的一個(gè) EventExecutor 執(zhí)行,并且之后這個(gè) handlers 的執(zhí)行一直都由這個(gè) EventExecutor 執(zhí)行,不再在 Netty 的內(nèi)核線程了!這個(gè)特性的使用需要精心去了解、適時(shí)使用,它對(duì)性能有重大幫助或影響。

四、總結(jié)

? ? ? ? 雖然 Netty 為網(wǎng)絡(luò)開(kāi)發(fā)提供了高性能的能力,以及簡(jiǎn)便的開(kāi)發(fā)框架和各種開(kāi)箱套件。但要寫(xiě)出良好的 Netty 程序,花點(diǎn)時(shí)間看下 Netty 的要點(diǎn)還是值得的。如果只是模模糊糊地使用 Netty 也總能被坑。

? ? ? ? 本文是市面上 第一個(gè)提出 Netty 內(nèi)核 概念的文章,希望借此有助于理解 Netty 的核心要點(diǎn)。Netty 的要點(diǎn)在其內(nèi)核體現(xiàn)了 Reactor 編碼架構(gòu),并根據(jù)實(shí)際需要進(jìn)行了擴(kuò)展。

? ? ? ? 以服務(wù)端程序?yàn)槔粋€(gè)應(yīng)用程序會(huì)包含一個(gè) 端口偵聽(tīng)內(nèi)核 以及 16 個(gè)連接讀寫(xiě)內(nèi)核(8 核 CPU下的默認(rèn)設(shè)置)。這些內(nèi)核具有同構(gòu)性。內(nèi)核包含一個(gè)內(nèi)部線程和隊(duì)列。代表服務(wù)端端口的 NioServerSocketChannel 或者代表客戶端的 NioSocketChannel 必須選擇注冊(cè)到某一個(gè)內(nèi)核中,內(nèi)核通過(guò)選擇器發(fā)現(xiàn)新的 I/O 事件的到來(lái),進(jìn)行初步加工,然后交給各自 channel 對(duì)應(yīng)的生產(chǎn)線去 pipeline 處理。應(yīng)用程序也會(huì)主動(dòng)發(fā)起一些工作,這些被稱為 Outbound 事件,比如往 channel 寫(xiě)入信息或者關(guān)閉 channel。這些 Outbound 事件也會(huì)由經(jīng)過(guò) pipleline ,最終再進(jìn)入內(nèi)核處理。

? ? ? ? ChannelPipeline 處理 Inbound 由內(nèi)核發(fā)起,處理 Outbound 事件由應(yīng)用程序發(fā)起,但是 pipeline 中的每個(gè)處理器在處理事件時(shí),都可以事先通過(guò) EventExecutorGroup 獲得的一個(gè) EventExecutor 執(zhí)行。對(duì)于那些耗時(shí)的工作,比如調(diào)用數(shù)據(jù)庫(kù)、遠(yuǎn)程服務(wù)的處理模塊,設(shè)置一個(gè)獨(dú)立于內(nèi)核線程的 EventExecutorGroup 是有絕對(duì)必要的。

2017-11-22

原文地址:http://www.itdecent.cn/p/045e01dc18e2

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • netty常用API學(xué)習(xí) netty簡(jiǎn)介 Netty是基于Java NIO的網(wǎng)絡(luò)應(yīng)用框架. Netty是一個(gè)NIO...
    花丶小偉閱讀 6,118評(píng)論 0 20
  • 本文是Netty文集中“Netty 源碼解析”系列的文章。主要對(duì)Netty的重要流程以及類(lèi)進(jìn)行源碼解析,以使得我們...
    tomas家的小撥浪鼓閱讀 2,733評(píng)論 2 8
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • 本文是Netty文集中“Netty 源碼解析”系列的文章。主要對(duì)Netty的重要流程以及類(lèi)進(jìn)行源碼解析,以使得我們...
    tomas家的小撥浪鼓閱讀 6,232評(píng)論 9 12
  • 我的2016 荒誕,不解,困惑。 愛(ài),生,死。 親情,友情,愛(ài)情。 高貴亦或卑微。 完整亦或撕裂。 善意亦或罪惡。...
    碧落之云閱讀 187評(píng)論 0 1

友情鏈接更多精彩內(nèi)容