最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網 會員登陸 & 注冊

解決Netty那些事兒之Reactor在Netty中的實現(創(chuàng)建篇)-下

2022-12-02 21:10 作者:補給站Linux內核  | 我要投稿

接上文解決Netty那些事兒之Reactor在Netty中的實現(創(chuàng)建篇)-上

Netty對JDK NIO 原生Selector的優(yōu)化

首先在NioEventLoop中有一個Selector優(yōu)化開關DISABLE_KEY_SET_OPTIMIZATION,通過系統(tǒng)變量-D io.netty.noKeySetOptimization指定,默認是開啟的,表示需要對JDK NIO原生Selector進行優(yōu)化。

如果優(yōu)化開關DISABLE_KEY_SET_OPTIMIZATION是關閉的,那么直接返回JDK NIO原生的Selector

下面為Netty對JDK NIO原生的Selector的優(yōu)化過程:

  1. 獲取JDK NIO原生Selector的抽象實現類sun.nio.ch.SelectorImplJDK NIO原生Selector的實現均繼承于該抽象類。用于判斷由SelectorProvider創(chuàng)建出來的Selector是否為JDK默認實現SelectorProvider第三種加載方式)。因為SelectorProvider可以是自定義加載,所以它創(chuàng)建出來的Selector并不一定是JDK NIO 原生的。

JDK NIO Selector的抽象類sun.nio.ch.SelectorImpl

這里筆者來簡單介紹下JDK NIO中的Selector中這幾個字段的含義,我們可以和上篇文章講到的epoll在內核中的結構做類比,方便大家后續(xù)的理解:

圖片
image.png
  • Set<SelectionKey> selectedKeys?類似于我們上篇文章講解Epoll時提到的就緒隊列eventpoll->rdllistSelector這里大家可以理解為Epoll。Selector會將自己監(jiān)聽到的IO就緒Channel放到selectedKeys中。

這里的SelectionKey暫且可以理解為ChannelSelector中的表示,類比上圖中epitem結構里的epoll_event,封裝IO就緒Socket的信息。其實SelectionKey里包含的信息不止是Channel還有很多IO相關的信息。后面我們在詳細介紹。

  • HashSet<SelectionKey> keys:這里存放的是所有注冊到該Selector上的Channel。類比epoll中的紅黑樹結構rb_root

SelectionKeyChannel注冊到Selector中后生成。

  • Set<SelectionKey> publicSelectedKeys?相當于是selectedKeys的視圖,用于向外部線程返回IO就緒SelectionKey。這個集合在外部線程中只能做刪除操作不可增加元素,并且不是線程安全的。

  • Set<SelectionKey> publicKeys相當于keys的不可變視圖,用于向外部線程返回所有注冊在該Selector上的SelectionKey

這里需要重點關注抽象類sun.nio.ch.SelectorImpl中的selectedKeyspublicSelectedKeys這兩個字段,注意它們的類型都是HashSet,一會優(yōu)化的就是這里?。。。?/strong>

  1. 判斷由SelectorProvider創(chuàng)建出來的Selector是否是JDK NIO原生的Selector實現。因為Netty優(yōu)化針對的是JDK NIO 原生Selector。判斷標準為sun.nio.ch.SelectorImpl類是否為SelectorProvider創(chuàng)建出Selector的父類。如果不是則直接返回。不在繼續(xù)下面的優(yōu)化過程。

通過前面對SelectorProvider的介紹我們知道,這里通過provider.openSelector()創(chuàng)建出來的Selector實現類為KQueueSelectorImpl類,它繼承實現了sun.nio.ch.SelectorImpl,所以它是JDK NIO 原生的Selector實現

  1. 創(chuàng)建SelectedSelectionKeySet通過反射替換掉sun.nio.ch.SelectorImpl類selectedKeyspublicSelectedKeys的默認HashSet實現。

為什么要用SelectedSelectionKeySet替換掉原來的HashSet呢??

因為這里涉及到對HashSet類型sun.nio.ch.SelectorImpl#selectedKeys集合的兩種操作:

  • 插入操作:?通過前邊對sun.nio.ch.SelectorImpl類中字段的介紹我們知道,在Selector監(jiān)聽到IO就緒SelectionKey后,會將IO就緒SelectionKey插入sun.nio.ch.SelectorImpl#selectedKeys集合中,這時Reactor線程會從java.nio.channels.Selector#select(long)阻塞調用中返回(類似上篇文章提到的epoll_wait)。

  • 遍歷操作:Reactor線程返回后,會從Selector中獲取IO就緒SelectionKey集合(也就是sun.nio.ch.SelectorImpl#selectedKeys),Reactor線程遍歷selectedKeys,獲取IO就緒SocketChannel,并處理SocketChannel上的IO事件。

我們都知道HashSet底層數據結構是一個哈希表,由于Hash沖突這種情況的存在,所以導致對哈希表進行插入遍歷操作的性能不如對數組進行插入遍歷操作的性能好。

還有一個重要原因是,數組可以利用CPU緩存的優(yōu)勢來提高遍歷的效率。后面筆者會有一篇專門的文章來講述利用CPU緩存行如何為我們帶來性能優(yōu)勢。

所以Netty為了優(yōu)化對sun.nio.ch.SelectorImpl#selectedKeys集合的插入,遍歷性能,自己用數組這種數據結構實現了SelectedSelectionKeySet,用它來替換原來的HashSet實現。



【文章福利】小編推薦自己的Linux內核技術交流群:【749907784】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ? ?


SelectedSelectionKeySet

  • 初始化SelectionKey[] keys數組大小為1024,當數組容量不夠時,擴容為原來的兩倍大小。

  • 通過數組尾部指針size,在向數組插入元素的時候可以直接定位到插入位置keys[size++]。操作一步到位,不用像哈希表那樣還需要解決Hash沖突。

  • 對數組的遍歷操作也是如絲般順滑,CPU直接可以在緩存行中遍歷讀取數組元素無需訪問內存。比HashSet的迭代器java.util.HashMap.KeyIterator?遍歷方式性能不知高到哪里去了。

看到這里不禁感嘆,從各種小的細節(jié)可以看出Netty對性能的優(yōu)化簡直淋漓盡致,對性能的追求令人發(fā)指。細節(jié)真的是魔鬼。

  1. Netty通過反射的方式用SelectedSelectionKeySet替換掉sun.nio.ch.SelectorImpl#selectedKeyssun.nio.ch.SelectorImpl#publicSelectedKeys這兩個集合中原來HashSet的實現。

  • 反射獲取sun.nio.ch.SelectorImpl類中selectedKeyspublicSelectedKeys

Java9版本以上通過sun.misc.Unsafe設置字段值的方式

通過反射的方式用SelectedSelectionKeySet替換掉hashSet實現的sun.nio.ch.SelectorImpl#selectedKeys,sun.nio.ch.SelectorImpl#publicSelectedKeys

將與sun.nio.ch.SelectorImpl類中selectedKeyspublicSelectedKeys關聯好的Netty優(yōu)化實現SelectedSelectionKeySet,設置到io.netty.channel.nio.NioEventLoop#selectedKeys字段中保存。

后續(xù)Reactor線程就會直接從io.netty.channel.nio.NioEventLoop#selectedKeys中獲取IO就緒SocketChannel

  1. SelectorTuple封裝unwrappedSelectorwrappedSelector返回給NioEventLoop構造函數。到此Reactor中的Selector就創(chuàng)建完畢了。

  • 所謂的unwrappedSelector是指被Netty優(yōu)化過的JDK NIO原生Selector。

  • 所謂的wrappedSelector就是用SelectedSelectionKeySetSelector裝飾類將unwrappedSelector和與sun.nio.ch.SelectorImpl類關聯好的Netty優(yōu)化實現SelectedSelectionKeySet封裝裝飾起來。

wrappedSelector會將所有對Selector的操作全部代理給unwrappedSelector,并在發(fā)起輪詢IO事件的相關操作中,重置SelectedSelectionKeySet清空上一次的輪詢結果。

到這里Reactor的核心Selector就創(chuàng)建好了,下面我們來看下用于保存異步任務的隊列是如何創(chuàng)建出來的。

newTaskQueue

我們繼續(xù)回到創(chuàng)建Reactor的主線上,到目前為止Reactor的核心Selector就創(chuàng)建好了,前邊我們提到Reactor除了需要監(jiān)聽IO就緒事件以及處理IO就緒事件外,還需要執(zhí)行一些異步任務,當外部線程向Reactor提交異步任務后,Reactor就需要一個隊列來保存這些異步任務,等待Reactor線程執(zhí)行。

下面我們來看下Reactor中任務隊列的創(chuàng)建過程:

  • NioEventLoop的父類SingleThreadEventLoop中提供了一個靜態(tài)變量DEFAULT_MAX_PENDING_TASKS用來指定Reactor任務隊列的大小??梢酝ㄟ^系統(tǒng)變量-D io.netty.eventLoop.maxPendingTasks進行設置,默認為Integer.MAX_VALUE,表示任務隊列默認為無界隊列。

  • 根據DEFAULT_MAX_PENDING_TASKS變量的設定,來決定創(chuàng)建無界任務隊列還是有界任務隊列。

Reactor內的異步任務隊列的類型為MpscQueue,它是由JCTools提供的一個高性能無鎖隊列,從命名前綴Mpsc可以看出,它適用于多生產者單消費者的場景,它支持多個生產者線程安全的訪問隊列,同一時刻只允許一個消費者線程讀取隊列中的元素。

我們知道Netty中的Reactor可以線程安全的處理注冊其上的多個SocketChannel上的IO數據,保證Reactor線程安全的核心原因正是因為這個MpscQueue,它可以支持多個業(yè)務線程在處理完業(yè)務邏輯后,線程安全的向MpscQueue添加異步寫任務,然后由單個Reactor線程來執(zhí)行這些寫任務。既然是單線程執(zhí)行,那肯定是線程安全的了。

Reactor對應的NioEventLoop類型繼承結構

圖片
image.png

NioEventLoop的繼承結構也是比較復雜,這里我們只關注在Reactor創(chuàng)建過程中涉及的到兩個父類SingleThreadEventLoop,SingleThreadEventExecutor。

剩下的繼承體系,我們在后邊隨著Netty源碼的深入在慢慢介紹。

前邊我們提到,其實Reactor我們可以看作是一個單線程的線程池,只有一個線程用來執(zhí)行IO就緒事件的監(jiān)聽,IO事件的處理,異步任務的執(zhí)行。用MpscQueue來存儲待執(zhí)行的異步任務。

命名前綴為SingleThread的父類都是對Reactor這些行為的分層定義。也是本小節(jié)要介紹的對象

SingleThreadEventLoop

Reactor負責執(zhí)行的異步任務分為三類:

  • 普通任務:這是Netty最主要執(zhí)行的異步任務,存放在普通任務隊列taskQueue中。在NioEventLoop構造函數中創(chuàng)建。

  • 定時任務:?存放在優(yōu)先級隊列中。后續(xù)我們介紹。

  • 尾部任務:?存放于尾部任務隊列tailTasks中,尾部任務一般不常用,在普通任務執(zhí)行完后 Reactor線程會執(zhí)行尾部任務。**使用場景:**比如對Netty 的運行狀態(tài)做一些統(tǒng)計數據,例如任務循環(huán)的耗時、占用物理內存的大小等等都可以向尾部隊列添加一個收尾任務完成統(tǒng)計數據的實時更新。

SingleThreadEventLoop負責對尾部任務隊列tailTasks進行管理。并且提供ChannelReactor注冊的行為。

SingleThreadEventExecutor

SingleThreadEventExecutor主要負責對普通任務隊列的管理,以及異步任務的執(zhí)行Reactor線程的啟停。

到現在為止,一個完整的Reactor架構就被創(chuàng)建出來了。

圖片
Reactor結構.png

3. 創(chuàng)建Channel到Reactor的綁定策略

到這一步,Reactor線程組NioEventLoopGroup里邊的所有Reactor就已經全部創(chuàng)建完畢。

無論是Netty服務端NioServerSocketChannel關注的OP_ACCEPT事件也好,還是Netty客戶端NioSocketChannel關注的OP_READOP_WRITE事件也好,都需要先注冊到Reactor上,Reactor才能監(jiān)聽Channel上關注的IO事件實現IO多路復用。

NioEventLoopGroup(Reactor線程組)里邊有眾多的Reactor,那么以上提到的這些Channel究竟應該注冊到哪個Reactor上呢?這就需要一個綁定的策略來平均分配。

還記得我們前邊介紹MultithreadEventExecutorGroup類的時候提到的構造器參數EventExecutorChooserFactory嗎?

這時候它就派上用場了,它主要用來創(chuàng)建ChannelReactor的綁定策略。默認為DefaultEventExecutorChooserFactory.INSTANCE。

下面我們來看下具體的綁定策略:

DefaultEventExecutorChooserFactory

我們看到在newChooser方法綁定策略有兩個分支,不同之處在于需要判斷Reactor線程組中的Reactor個數是否為2的次冪

Netty中的綁定策略就是采用round-robin輪詢的方式來挨個選擇Reactor進行綁定。

采用round-robin的方式進行負載均衡,我們一般會用round % reactor.length取余的方式來挨個平均的定位到對應的Reactor上。

如果Reactor的個數reactor.length恰好是2的次冪,那么就可以用位操作&運算round & reactor.length -1來代替%運算round % reactor.length,因為位運算的性能更高。具體為什么&運算能夠代替%運算,筆者會在后面講述時間輪的時候為大家詳細證明,這里大家只需記住這個公式,我們還是聚焦本文的主線。

了解了優(yōu)化原理,我們在看代碼實現就很容易理解了。

利用%運算的方式Math.abs(idx.getAndIncrement() % executors.length)來進行綁定。

利用&運算的方式idx.getAndIncrement() & executors.length - 1來進行綁定。

又一次被Netty對性能的極致追求所折服~~~~

4. 向Reactor線程組中所有的Reactor注冊terminated回調函數

當Reactor線程組NioEventLoopGroup中所有的Reactor已經創(chuàng)建完畢,ChannelReactor的綁定策略也創(chuàng)建完畢后,我們就來到了創(chuàng)建NioEventGroup的最后一步。

俗話說的好,有創(chuàng)建就有啟動,有啟動就有關閉,這里會創(chuàng)建Reactor關閉的回調函數terminationListener,在Reactor關閉時回調。

terminationListener回調的邏輯很簡單:

  • 通過AtomicInteger terminatedChildren變量記錄已經關閉的Reactor個數,用來判斷NioEventLoopGroup中的Reactor是否已經全部關閉。

  • 如果所有Reactor均已關閉,設置NioEventLoopGroup中的terminationFuturesuccess。表示Reactor線程組關閉成功。

我們在回到文章開頭Netty服務端代碼模板

現在Netty的主從Reactor線程組就已經創(chuàng)建完畢,此時Netty服務端的骨架已經搭建完畢,骨架如下:

圖片
主從Reactor線程組.png

總結

本文介紹了首先介紹了Netty對各種IO模型的支持以及如何輕松切換各種IO模型。

還花了大量的篇幅介紹Netty服務端的核心引擎主從Reactor線程組的創(chuàng)建過程。在這個過程中,我們還提到了Netty對各種細節(jié)進行的優(yōu)化,展現了Netty對性能極致的追求。

好了,Netty服務端的骨架已經搭好,剩下的事情就該綁定端口地址然后接收連接了.


原文作者:bin的技術小屋



解決Netty那些事兒之Reactor在Netty中的實現(創(chuàng)建篇)-下的評論 (共 條)

分享到微博請遵守國家法律
曲水县| 福安市| 长顺县| 平泉县| 临沧市| 油尖旺区| 西丰县| 盖州市| 汝南县| 临猗县| 武平县| 松原市| 嫩江县| 新民市| 东海县| 鄢陵县| 本溪| 五华县| 海原县| 独山县| 郸城县| 元谋县| 瓮安县| 云南省| 安康市| 溆浦县| 泰兴市| 厦门市| 醴陵市| 仁怀市| 定州市| 五河县| 柳林县| 乌兰察布市| 遂宁市| 兴和县| 光泽县| 怀集县| 香河县| 海淀区| 田东县|