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

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

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

2022-12-02 16:48 作者:補給站Linux內(nèi)核  | 我要投稿

本系列Netty源碼解析文章基于?4.1.56.Final版本

在上篇文章《聊聊Netty那些事兒之從內(nèi)核角度看IO模型》中我們花了大量的篇幅來從內(nèi)核角度詳細講述了五種IO模型的演進過程以及ReactorIO線程模型的底層基石IO多路復用技術(shù)在內(nèi)核中的實現(xiàn)原理。

最后我們引出了netty中使用的主從Reactor IO線程模型。

圖片
netty中的reactor.png

通過上篇文章的介紹,我們已經(jīng)清楚了在IO調(diào)用的過程中內(nèi)核幫我們搞了哪些事情,那么俗話說的好內(nèi)核領(lǐng)進門,修行在netty,netty在用戶空間又幫我們搞了哪些事情?

那么從本文開始,筆者將從源碼角度來帶大家看下上圖中的Reactor IO線程模型在Netty中是如何實現(xiàn)的。

本文作為Reactor在Netty中實現(xiàn)系列文章中的開篇文章,筆者先來為大家介紹Reactor的骨架是如何創(chuàng)建出來的。

在上篇文章中我們提到Netty采用的是主從Reactor多線程的模型,但是它在實現(xiàn)上又與Doug Lea在Scalable IO in Java論文中提到的經(jīng)典主從Reactor多線程模型有所差異。

圖片
經(jīng)典主從Reactor多線程模型

Netty中的Reactor是以Group的形式出現(xiàn)的,主從Reactor在Netty中就是主從Reactor組,每個Reactor Group中會有多個Reactor用來執(zhí)行具體的IO任務(wù)。當然在netty中Reactor不只用來執(zhí)行IO任務(wù),這個我們后面再說。

  • Main Reactor Group中的Reactor數(shù)量取決于服務(wù)端要監(jiān)聽的端口個數(shù),通常我們的服務(wù)端程序只會監(jiān)聽一個端口,所以Main Reactor Group只會有一個Main Reactor線程來處理最重要的事情:綁定端口地址,接收客戶端連接,為客戶端創(chuàng)建對應(yīng)的SocketChannel將客戶端SocketChannel分配給一個固定的Sub Reactor。也就是上篇文章筆者為大家舉的例子,飯店最重要的工作就是先把客人迎接進來。“我家大門常打開,開放懷抱等你,擁抱過就有了默契你會愛上這里......”

圖片
我家大門常打開,開放懷抱等你.png
  • Sub Reactor Group里有多個Reactor線程,Reactor線程的個數(shù)可以通過系統(tǒng)參數(shù)-D io.netty.eventLoopThreads指定。默認的Reactor的個數(shù)為CPU核數(shù) * 2。Sub Reactor線程主要用來輪詢客戶端SocketChannel上的IO就緒事件處理IO就緒事件,執(zhí)行異步任務(wù)。Sub Reactor Group做的事情就是上篇飯店例子中服務(wù)員的工作,客人進來了要為客人分配座位,端茶送水,做菜上菜。“不管遠近都是客人,請不用客氣,相約好了在一起,我們歡迎您......”

圖片
我們歡迎您..png

一個客戶端SocketChannel只能分配給一個固定的Sub Reactor。一個Sub Reactor負責處理多個客戶端SocketChannel,這樣可以將服務(wù)端承載的全量客戶端連接分攤到多個Sub Reactor中處理,同時也能保證客戶端SocketChannel上的IO處理的線程安全性。

由于文章篇幅的關(guān)系,作為Reactor在netty中實現(xiàn)的第一篇我們主要來介紹主從Reactor Group的創(chuàng)建流程,骨架脈絡(luò)先搭好。

下面我們來看一段Netty服務(wù)端代碼的編寫模板,從代碼模板的流程中我們來解析下主從Reactor的創(chuàng)建流程以及在這個過程中所涉及到的Netty核心類。



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

Netty服務(wù)端代碼模板

首先我們要創(chuàng)建Netty最核心的部分 ->?創(chuàng)建主從Reactor Group,在Netty中EventLoopGroup就是Reactor Group的實現(xiàn)類。對應(yīng)的EventLoop就是Reactor的實現(xiàn)類。

創(chuàng)建用于IO處理ChannelHandler,實現(xiàn)相應(yīng)IO事件的回調(diào)函數(shù),編寫對應(yīng)的IO處理邏輯。注意這里只是簡單示例哈,詳細的IO事件處理,筆者會單獨開一篇文章專門講述。

  1. 創(chuàng)建ServerBootstrapNetty服務(wù)端啟動類,并在啟動類中配置啟動Netty服務(wù)端所需要的一些必備信息。

    在上篇文章介紹Socket內(nèi)核結(jié)構(gòu)小節(jié)中我們提到,在編寫服務(wù)端網(wǎng)絡(luò)程序時,我們首先要創(chuàng)建一個Socket用于listen和bind端口地址,我們把這個叫做監(jiān)聽Socket,這里對應(yīng)的就是NioServerSocketChannel.class。當客戶端連接完成三次握手,系統(tǒng)調(diào)用accept函數(shù)會基于監(jiān)聽Socket創(chuàng)建出來一個新的Socket專門用于與客戶端之間的網(wǎng)絡(luò)通信我們稱為客戶端連接Socket,這里對應(yīng)的就是NioSocketChannel.class

    netty有兩種Channel類型:一種是服務(wù)端用于監(jiān)聽綁定端口地址的NioServerSocketChannel,一種是用于客戶端通信的NioSocketChannel。每種Channel類型實例都會對應(yīng)一個PipeLine用于編排對應(yīng)channel實例上的IO事件處理邏輯。PipeLine中組織的就是ChannelHandler用于編寫特定的IO處理邏輯。

    注意serverBootstrap.handler設(shè)置的是服務(wù)端NioServerSocketChannel PipeLine中的ChannelHandler。

    ServerBootstrap啟動類方法帶有child前綴的均是設(shè)置客戶端NioSocketChannel屬性的。

    ChannelInitializer是用于當SocketChannel成功注冊到綁定的Reactor上后,用于初始化該SocketChannelPipeline。它的initChannel方法會在注冊成功后執(zhí)行。這里只是捎帶提一下,讓大家有個初步印象,后面我會專門介紹。

    • serverBootstrap.childHandler(ChannelHandler childHandler)用于設(shè)置客戶端NioSocketChannel中對應(yīng)Pipieline中的ChannelHandler。我們通常配置的編碼解碼器就是在這里。

    • serverBootstrap.option(ChannelOption.SO_BACKLOG, 100)設(shè)置服務(wù)端ServerSocketChannel中的SocketOption。關(guān)于SocketOption的選項我們后邊的文章再聊,本文主要聚焦在Netty?Main Reactor Group的創(chuàng)建及工作流程。

    • serverBootstrap.handler(....)設(shè)置服務(wù)端NioServerSocketChannel中對應(yīng)Pipieline中的ChannelHandler

    • 通過serverBootstrap.group(bossGroup, workerGroup)為Netty服務(wù)端配置主從Reactor Group實例。

    • 通過serverBootstrap.channel(NioServerSocketChannel.class)配置Netty服務(wù)端的ServerSocketChannel用于綁定端口地址以及創(chuàng)建客戶端SocketChannel。Netty中的NioServerSocketChannel.class就是對JDK NIO中ServerSocketChannel的封裝。而用于表示客戶端連接NioSocketChannel是對JDK NIO?SocketChannel封裝。

  2. ChannelFuture f = serverBootstrap.bind(PORT).sync()這一步會是下篇文章要重點分析的主題Main Reactor Group的啟動,綁定端口地址,開始監(jiān)聽客戶端連接事件(OP_ACCEPT)。本文我們只關(guān)注創(chuàng)建流程。

  3. f.channel().closeFuture().sync()等待服務(wù)端NioServerSocketChannel關(guān)閉。Netty服務(wù)端到這里正式啟動,并準備好接受客戶端連接的準備。

  4. shutdownGracefully優(yōu)雅關(guān)閉主從Reactor線程組里的所有Reactor線程。

Netty對IO模型的支持

在上篇文章中我們介紹了五種IO模型,Netty中支持BIO,NIO,AIO以及多種操作系統(tǒng)下的IO多路復用技術(shù)實現(xiàn)。

在Netty中切換這幾種IO模型也是非常的方便,下面我們來看下Netty如何對這幾種IO模型進行支持的。

首先我們介紹下幾個與IO模型相關(guān)的重要接口:

EventLoop

EventLoop就是Netty中的Reactor,可以說它就是Netty的引擎,負責Channel上IO就緒事件的監(jiān)聽,IO就緒事件的處理,異步任務(wù)的執(zhí)行驅(qū)動著整個Netty的運轉(zhuǎn)。

不同IO模型下,EventLoop有著不同的實現(xiàn),我們只需要切換不同的實現(xiàn)類就可以完成對NettyIO模型的切換。

NIO模型下Netty會自動根據(jù)操作系統(tǒng)以及版本的不同選擇對應(yīng)的IO多路復用技術(shù)實現(xiàn)。比如Linux 2.6版本以上用的是Epoll,2.6版本以下用的是Poll,Mac下采用的是Kqueue

其中Linux kernel 在5.1版本引入的異步IO庫io_uring正在netty中孵化。

EventLoopGroup

Netty中的Reactor是以Group的形式出現(xiàn)的,EventLoopGroup正是Reactor組的接口定義,負責管理Reactor,Netty中的Channel就是通過EventLoopGroup注冊到具體的Reactor上的。

Netty的IO線程模型是主從Reactor多線程模型,主從Reactor線程組在Netty源碼中對應(yīng)的其實就是兩個EventLoopGroup實例。

不同的IO模型也有對應(yīng)的實現(xiàn):

SocketChannel

用于與客戶端通信的SocketChannel,對應(yīng)于上篇文章提到的客戶端連接Socket,當客戶端完成三次握手后,由系統(tǒng)調(diào)用accept函數(shù)根據(jù)監(jiān)聽Socket創(chuàng)建。

不同的IO模型下的實現(xiàn):

我們看到在不同IO模型的實現(xiàn)中,Netty這些圍繞IO模型的核心類只是前綴的不同:

  • BIO對應(yīng)的前綴為Oio表示old io,現(xiàn)在已經(jīng)廢棄不推薦使用。

  • NIO對應(yīng)的前綴為Nio,正是Netty推薦也是我們常用的非阻塞IO模型。

  • AIO對應(yīng)的前綴為Aio,由于Linux下的異步IO機制實現(xiàn)的并不成熟,性能提升表現(xiàn)上也不明顯,現(xiàn)已被刪除。

我們只需要將IO模型的這些核心接口對應(yīng)的實現(xiàn)類前綴改為對應(yīng)IO模型的前綴,就可以輕松在Netty中完成對IO模型的切換。

圖片
image.png

多種NIO的實現(xiàn)

我們通常在使用NIO模型的時候會使用Common列下的這些IO模型核心類,Common類也會根據(jù)操作系統(tǒng)的不同自動選擇JDK在對應(yīng)平臺下的IO多路復用技術(shù)的實現(xiàn)。

而Netty自身也根據(jù)操作系統(tǒng)的不同提供了自己對IO多路復用技術(shù)的實現(xiàn),比JDK的實現(xiàn)性能更優(yōu)。比如:

  • JDK的 NIO?默認實現(xiàn)是水平觸發(fā),Netty 是邊緣觸發(fā)(默認)和水平觸發(fā)可切換。。

  • Netty 實現(xiàn)的垃圾回收更少、性能更好。

我們編寫Netty服務(wù)端程序的時候也可以根據(jù)操作系統(tǒng)的不同,采用Netty自身的實現(xiàn)來進一步優(yōu)化程序。做法也很簡單,直接將上圖中紅框里的實現(xiàn)類替換成Netty的自身實現(xiàn)類即可完成切換。

經(jīng)過以上對Netty服務(wù)端代碼編寫模板以及IO模型相關(guān)核心類的簡單介紹,我們對Netty的創(chuàng)建流程有了一個簡單粗略的總體認識,下面我們來深入剖析下創(chuàng)建流程過程中的每一個步驟以及這個過程中涉及到的核心類實現(xiàn)。

以下源碼解析部分我們均采用Common列NIO相關(guān)的實現(xiàn)進行解析。

創(chuàng)建主從Reactor線程組

在Netty服務(wù)端程序編寫模板的開始,我們首先會創(chuàng)建兩個Reactor線程組:

圖片
netty中的reactor.png
  • 一個是主Reactor線程組bossGroup用于監(jiān)聽客戶端連接,創(chuàng)建客戶端連接NioSocketChannel,并將創(chuàng)建好的客戶端連接NioSocketChannel注冊到從Reactor線程組中一個固定的Reactor上。

  • 一個是從Reactor線程組workerGroup,workerGroup中的Reactor負責監(jiān)聽綁定在其上的客戶端連接NioSocketChannel上的IO就緒事件,并處理IO就緒事件執(zhí)行異步任務(wù)。

Netty中Reactor線程組的實現(xiàn)類為NioEventLoopGroup,在創(chuàng)建bossGroupworkerGroup的時候用到了NioEventLoopGroup的兩個構(gòu)造函數(shù):

  • nThreads參數(shù)的構(gòu)造函數(shù)public NioEventLoopGroup(int nThreads)

  • 不帶nThreads參數(shù)的默認構(gòu)造函數(shù)public NioEventLoopGroup()

nThreads參數(shù)表示當前要創(chuàng)建的Reactor線程組內(nèi)包含多少個Reactor線程。不指定nThreads參數(shù)的話采用默認的Reactor線程個數(shù),用0表示。

最終會調(diào)用到構(gòu)造函數(shù)

下面簡單介紹下構(gòu)造函數(shù)中這幾個參數(shù)的作用,后面我們在講解本文主線的過程中還會提及這幾個參數(shù),到時在詳細介紹,這里只是讓大家有個初步印象,不必做過多的糾纏。

  • Executor executor:負責啟動Reactor線程進而Reactor才可以開始工作。

Reactor線程組NioEventLoopGroup負責創(chuàng)建Reactor線程,在創(chuàng)建的時候會將executor傳入。

  • RejectedExecutionHandler:?當向Reactor添加異步任務(wù)添加失敗時,采用的拒絕策略。Reactor的任務(wù)不只是監(jiān)聽IO活躍事件和IO任務(wù)的處理,還包括對異步任務(wù)的處理。這里大家只需有個這樣的概念,后面筆者會專門詳細介紹。

  • SelectorProvider selectorProvider:?Reactor中的IO模型為IO多路復用模型,對應(yīng)于JDK NIO中的實現(xiàn)為java.nio.channels.Selector(就是我們上篇文章中提到的select,poll,epoll),每個Reator中都包含一個Selector,用于輪詢注冊在該Reactor上的所有Channel上的IO事件。SelectorProvider就是用來創(chuàng)建Selector的。

  • SelectStrategyFactory selectStrategyFactory:?Reactor最重要的事情就是輪詢注冊其上的Channel上的IO就緒事件,這里的SelectStrategyFactory用于指定輪詢策略,默認為DefaultSelectStrategyFactory.INSTANCE。

最終會將這些參數(shù)交給NioEventLoopGroup的父類構(gòu)造器,下面我們來看下NioEventLoopGroup類的繼承結(jié)構(gòu):

圖片
image.png

NioEventLoopGroup類的繼承結(jié)構(gòu)乍一看比較復雜,大家不要慌,筆者會隨著主線的深入慢慢地介紹這些父類接口,我們現(xiàn)在重點關(guān)注Mutithread前綴的類。

我們知道NioEventLoopGroup是Netty中的Reactor線程組的實現(xiàn),既然是線程組那么肯定是負責管理和創(chuàng)建多個Reactor線程的,所以Mutithread前綴的類定義的行為自然是對Reactor線程組內(nèi)多個Reactor線程的創(chuàng)建和管理工作。

MultithreadEventLoopGroup

MultithreadEventLoopGroup類主要的功能就是用來確定Reactor線程組內(nèi)Reactor的個數(shù)。

默認的Reactor的個數(shù)存放于字段DEFAULT_EVENT_LOOP_THREADS中。

static {}靜態(tài)代碼塊中我們可以看出默認Reactor的個數(shù)的獲取邏輯:

  • 可以通過系統(tǒng)變量?-D io.netty.eventLoopThreads"指定。

  • 如果不指定,那么默認的就是NettyRuntime.availableProcessors() * 2

nThread參數(shù)設(shè)置為0采用默認設(shè)置時,Reactor線程組內(nèi)的Reactor個數(shù)則設(shè)置為DEFAULT_EVENT_LOOP_THREADS。

MultithreadEventExecutorGroup

MultithreadEventExecutorGroup這里就是本小節(jié)的核心,主要用來定義創(chuàng)建和管理Reactor的行為。

首先介紹一個新的構(gòu)造器參數(shù)EventExecutorChooserFactory chooserFactory。當客戶端連接完成三次握手后,Main Reactor會創(chuàng)建客戶端連接NioSocketChannel,并將其綁定到Sub Reactor Group中的一個固定Reactor,那么具體要綁定到哪個具體的Sub Reactor上呢?這個綁定策略就是由chooserFactory來創(chuàng)建的。默認為DefaultEventExecutorChooserFactory

下面就是本小節(jié)的主題Reactor線程組的創(chuàng)建過程:

1. 創(chuàng)建用于啟動Reactor線程的executor

在Netty Reactor Group中的單個ReactorIO線程模型為上篇文章提到的單Reactor單線程模型,一個Reactor線程負責輪詢注冊其上的所有Channel中的IO就緒事件,處理IO事件,執(zhí)行Netty中的異步任務(wù)等工作。正是這個Reactor線程驅(qū)動著整個Netty的運轉(zhuǎn),可謂是Netty的核心引擎。

圖片
單Reactor單線程模型.png

而這里的executor就是負責啟動Reactor線程的,從創(chuàng)建源碼中我們可以看到executor的類型為ThreadPerTaskExecutor。

ThreadPerTaskExecutor

我們看到ThreadPerTaskExecutor做的事情很簡單,從它的命名前綴ThreadPerTask我們就可以猜出它的工作方式,就是來一個任務(wù)就創(chuàng)建一個線程執(zhí)行。而創(chuàng)建的這個線程正是netty的核心引擎Reactor線程。

Reactor線程啟動的時候,Netty會將Reactor線程要做的事情封裝成Runnable,丟給exexutor啟動。

Reactor線程的核心就是一個死循環(huán)不停的輪詢IO就緒事件,處理IO事件,執(zhí)行異步任務(wù)。一刻也不停歇,堪稱996典范。

這里向大家先賣個關(guān)子,"Reactor線程是何時啟動的呢??"

2. 創(chuàng)建Reactor

Reactor線程組NioEventLoopGroup包含多個Reactor,存放于private final EventExecutor[] children數(shù)組中。

所以下面的事情就是創(chuàng)建nThreadReactor,并存放于EventExecutor[] children字段中,

我們來看下用于創(chuàng)建ReactornewChild(executor, args)方法:

newChild

newChild方法是MultithreadEventExecutorGroup中的一個抽象方法,提供給具體子類實現(xiàn)。

這里我們解析的是NioEventLoopGroup,我們來看下newChild在該類中的實現(xiàn):

前邊提到的眾多構(gòu)造器參數(shù),這里會通過可變參數(shù)Object... args傳入到Reactor類NioEventLoop的構(gòu)造器中。

這里介紹下新的參數(shù)EventLoopTaskQueueFactory queueFactory,前邊提到Netty中的Reactor主要工作是輪詢注冊其上的所有Channel上的IO就緒事件,處理IO就緒事件。除了這些主要的工作外,Netty為了極致的壓榨Reactor的性能,還會讓它做一些異步任務(wù)的執(zhí)行工作。既然要執(zhí)行異步任務(wù),那么Reactor中就需要一個隊列來保存任務(wù)。

這里的EventLoopTaskQueueFactory就是用來創(chuàng)建這樣的一個隊列來保存Reactor中待執(zhí)行的異步任務(wù)。

可以把Reactor理解成為一個單線程的線程池,類似JDK中的SingleThreadExecutor,僅用一個線程來執(zhí)行輪詢IO就緒事件,處理IO就緒事件執(zhí)行異步任務(wù)。同時待執(zhí)行的異步任務(wù)保存在Reactor里的taskQueue中。

NioEventLoop

這里就正式開始了Reactor的創(chuàng)建過程,我們知道Reactor的核心是采用的IO多路復用模型來對客戶端連接上的IO事件進行監(jiān)聽,所以最重要的事情是創(chuàng)建Selector(JDK NIO 中IO多路復用技術(shù)的實現(xiàn))。

可以把Selector理解為我們上篇文章介紹的Select,poll,epoll,它是JDK NIO對操作系統(tǒng)內(nèi)核提供的這些IO多路復用技術(shù)的封裝。

openSelector

openSelectorNioEventLoop類中用于創(chuàng)建IO多路復用Selector,并對創(chuàng)建出來的JDK NIO?原生的Selector進行性能優(yōu)化。

首先會通過SelectorProvider#openSelector創(chuàng)建JDK NIO原生的Selector。

SelectorProvider會根據(jù)操作系統(tǒng)的不同選擇JDK在不同操作系統(tǒng)版本下的對應(yīng)Selector的實現(xiàn)。Linux下會選擇Epoll,Mac下會選擇Kqueue

下面我們就來看下SelectorProvider是如何做到自動適配不同操作系統(tǒng)下IO多路復用實現(xiàn)的

SelectorProvider

SelectorProvider是在前面介紹的NioEventLoopGroup類構(gòu)造函數(shù)中通過調(diào)用SelectorProvider.provider()被加載,并通過NioEventLoopGroup#newChild方法中的可變長參數(shù)Object... args傳遞到NioEventLoop中的private final SelectorProvider provider字段中。

SelectorProvider的加載過程:

SelectorProvider加載源碼中我們可以看出,SelectorProvider的加載方式有三種,優(yōu)先級如下:

  1. 通過系統(tǒng)變量-D java.nio.channels.spi.SelectorProvider指定SelectorProvider的自定義實現(xiàn)類全限定名。通過應(yīng)用程序類加載器(Application Classloader)加載。

通過SPI方式加載。在工程目錄META-INF/services下定義名為java.nio.channels.spi.SelectorProviderSPI文件,文件中第一個定義的SelectorProvider實現(xiàn)類全限定名就會被加載。


如果以上兩種方式均未被定義,那么就采用SelectorProvider系統(tǒng)默認實現(xiàn)sun.nio.ch.DefaultSelectorProvider。筆者當前使用的操作系統(tǒng)是MacOS,從源碼中我們可以看到自動適配了KQueue實現(xiàn)。

不同操作系統(tǒng)中JDK對于DefaultSelectorProvider會有所不同,Linux內(nèi)核版本2.6以上對應(yīng)的Epoll,Linux內(nèi)核版本2.6以下對應(yīng)的Poll,MacOS對應(yīng)的是KQueue。

下面我們接著回到io.netty.channel.nio.NioEventLoop#openSelector的主線上來。


文章篇幅有限 下文繼續(xù)講解

原文作者:bin的技術(shù)小屋



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

分享到微博請遵守國家法律
金山区| 镇巴县| 新蔡县| 华宁县| 富裕县| 霍林郭勒市| 内丘县| 青海省| 运城市| 甘德县| 乌拉特中旗| 望城县| 永和县| 青海省| 武强县| 普兰县| 屏南县| 灌南县| 遂宁市| 大余县| 溧阳市| 岐山县| 慈溪市| 白沙| 正镶白旗| 根河市| 仁化县| 吉安县| 新绛县| 墨脱县| 瑞昌市| 崇左市| 琼海市| 丁青县| 剑河县| 大渡口区| 宁远县| 松阳县| 仙游县| 乐至县| 黄山市|