Netty 那些事兒 ——— Reactor模式詳解

本文是Netty文集中“Netty 那些事兒”系列的文章。主要結(jié)合在開發(fā)實(shí)戰(zhàn)中,我們遇到的一些“奇奇怪怪”的問題,以及如何正確且更好的使用Netty框架,并會(huì)對(duì)Netty中涉及的重要設(shè)計(jì)理念進(jìn)行介紹。

在學(xué)習(xí)Reactor模式之前,我們需要對(duì)“I/O的四種模型”以及“什么是I/O多路復(fù)用”進(jìn)行簡(jiǎn)單的介紹,因?yàn)镽eactor是一個(gè)使用了同步非阻塞的I/O多路復(fù)用機(jī)制的模式。

I/O的四種模型

I/0 操作 主要分成兩部分
① 數(shù)據(jù)準(zhǔn)備,將數(shù)據(jù)加載到內(nèi)核緩存
② 將內(nèi)核緩存中的數(shù)據(jù)加載到用戶緩存

  • Synchronous blocking I/O

    Typical flow of the synchronous blocking I/O model

  • Synchronous non-blocking I/0

    Typical flow of the synchronous non-blocking I/O model

  • Asynchronous blocking I/0

    Typical flow of the asynchronous blocking I/O model (select)

  • Asynchronous non-blocking I/0

    Typical flow of the asynchronous non-blocking I/O model

堵塞、非堵塞的區(qū)別是在于第一階段,即數(shù)據(jù)準(zhǔn)備階段。無論是堵塞還是非堵塞,都是用應(yīng)用主動(dòng)找內(nèi)核要數(shù)據(jù),而read數(shù)據(jù)的過程是‘堵塞’的,直到數(shù)據(jù)讀取完。
同步、異步的區(qū)別在于第二階段,若由請(qǐng)求者主動(dòng)的去獲取數(shù)據(jù),則為同步操作,需要說明的是:read/write操作也是‘堵塞’的,直到數(shù)據(jù)讀取完。
若數(shù)據(jù)的read都由kernel內(nèi)核完成了(在內(nèi)核read數(shù)據(jù)的過程中,應(yīng)用進(jìn)程依舊可以執(zhí)行其他的任務(wù)),這就是異步操作。

換句話說,BIO里用戶最關(guān)心“我要讀”,NIO里用戶最關(guān)心"我可以讀了",在AIO模型里用戶更需要關(guān)注的是“讀完了”。
NIO一個(gè)重要的特點(diǎn)是:socket主要的讀、寫、注冊(cè)和接收函數(shù),在等待就緒階段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。
NIO是一種同步非阻塞的I/O模型,也是I/O多路復(fù)用的基礎(chǔ)。

I/O多路復(fù)用

I/O多路復(fù)用是指使用一個(gè)線程來檢查多個(gè)文件描述符(Socket)的就緒狀態(tài),比如調(diào)用select和poll函數(shù),傳入多個(gè)文件描述符,如果有一個(gè)文件描述符就緒,則返回,否則阻塞直到超時(shí)。得到就緒狀態(tài)后進(jìn)行真正的操作可以在同一個(gè)線程里執(zhí)行,也可以啟動(dòng)線程執(zhí)行(比如使用線程池)。

一般情況下,I/O 復(fù)用機(jī)制需要事件分發(fā)器。 事件分發(fā)器的作用,將那些讀寫事件源分發(fā)給各讀寫事件的處理者。
涉及到事件分發(fā)器的兩種模式稱為:Reactor和Proactor。 Reactor模式是基于同步I/O的,而Proactor模式是和異步I/O相關(guān)的。本文主要介紹的就是 Reactor模式相關(guān)的知識(shí)。

經(jīng)典的I/O服務(wù)設(shè)計(jì) ———— BIO模式

??這就是經(jīng)典的每連接對(duì)應(yīng)一個(gè)線程的同步阻塞I/O模式。

  • 流程:
    ① 服務(wù)器端的Server是一個(gè)線程,線程中執(zhí)行一個(gè)死循環(huán)來阻塞的監(jiān)聽客戶端的連接請(qǐng)求和通信。
    ② 當(dāng)客戶端向服務(wù)器端發(fā)送一個(gè)連接請(qǐng)求后,服務(wù)器端的Server會(huì)接受客戶端的請(qǐng)求,ServerSocket.accept()從阻塞中返回,得到一個(gè)與客戶端連接相對(duì)于的Socket。
    ③ 構(gòu)建一個(gè)handler,將Socket傳入該handler。創(chuàng)建一個(gè)線程并啟動(dòng)該線程,在線程中執(zhí)行handler,這樣與客戶端的所有的通信以及數(shù)據(jù)處理都在該線程中執(zhí)行。當(dāng)該客戶端和服務(wù)器端完成通信關(guān)閉連接后,線程就會(huì)被銷毀。
    ④ 然后Server繼續(xù)執(zhí)行accept()操作等待新的連接請(qǐng)求。

  • 優(yōu)點(diǎn):
    ① 使用簡(jiǎn)單,容易編程
    ② 在多核系統(tǒng)下,能夠充分利用了多核CPU的資源。即,當(dāng)I/O阻塞系統(tǒng),但CPU空閑的時(shí)候,可以利用多線程使用CPU資源。

  • 缺點(diǎn):
    該模式的本質(zhì)問題在于嚴(yán)重依賴線程,但線程Java虛擬機(jī)非常寶貴的資源。隨著客戶端并發(fā)訪問量的急劇增加,線程數(shù)量的不斷膨脹將服務(wù)器端的性能將急劇下降。
    ① 線程生命周期的開銷非常高。線程的創(chuàng)建與銷毀并不是沒有代價(jià)的。在Linux這樣的操作系統(tǒng)中,線程本質(zhì)上就是一個(gè)進(jìn)程,創(chuàng)建和銷毀都是重量級(jí)的系統(tǒng)函數(shù)。
    ② 資源消耗。內(nèi)存:大量空閑的線程會(huì)占用許多內(nèi)存,給垃圾回收器帶來壓力。;CPU:如果你已經(jīng)擁有足夠多的線程使所有CPU保持忙碌狀態(tài),那么再創(chuàng)建更過的線程反而會(huì)降低性能。
    ③ 穩(wěn)定性。在可創(chuàng)建線程的數(shù)量上存在一個(gè)限制。這個(gè)限制值將隨著平臺(tái)的不同而不同,并且受多個(gè)因素制約:a)JVM的啟動(dòng)參數(shù)、b)Threa的構(gòu)造函數(shù)中請(qǐng)求的棧大小、c)底層操作系統(tǒng)對(duì)線程的限制 等。如果破壞了這些限制,那么很可能拋出OutOfMemoryError異常。
    ④ 線程的切換成本是很高的。操作系統(tǒng)發(fā)生線程切換的時(shí)候,需要保留線程的上下文,然后執(zhí)行系統(tǒng)調(diào)用。如果線程數(shù)過高,不僅會(huì)帶來許多無用的上下文切換,還可能導(dǎo)致執(zhí)行線程切換的時(shí)間甚至?xí)笥诰€程執(zhí)行的時(shí)間,這時(shí)候帶來的表現(xiàn)往往是系統(tǒng)負(fù)載偏高、CPU sy(系統(tǒng)CPU)使用率特別高,導(dǎo)致系統(tǒng)幾乎陷入不可用的狀態(tài)。
    ⑤ 容易造成鋸齒狀的系統(tǒng)負(fù)載。一旦線程數(shù)量高但外部網(wǎng)絡(luò)環(huán)境不是很穩(wěn)定,就很容易造成大量請(qǐng)求的結(jié)果同時(shí)返回,激活大量阻塞線程從而使系統(tǒng)負(fù)載壓力過大。
    ⑥ 若是長(zhǎng)連接的情況下并且客戶端與服務(wù)器端交互并不頻繁的,那么客戶端和服務(wù)器端的連接會(huì)一直保留著,對(duì)應(yīng)的線程也就一直存在在,但因?yàn)椴活l繁的通信,導(dǎo)致大量線程在大量時(shí)間內(nèi)都處于空置狀態(tài)。

  • 適用場(chǎng)景:如果你有少量的連接使用非常高的帶寬,一次發(fā)送大量的數(shù)據(jù),也許典型的IO服務(wù)器實(shí)現(xiàn)可能非常契合。

Reactor模式

Reactor模式(反應(yīng)器模式)是一種處理一個(gè)或多個(gè)客戶端并發(fā)交付服務(wù)請(qǐng)求的事件設(shè)計(jì)模式。當(dāng)請(qǐng)求抵達(dá)后,服務(wù)處理程序使用I/O多路復(fù)用策略,然后同步地派發(fā)這些請(qǐng)求至相關(guān)的請(qǐng)求處理程序。

Reactor結(jié)構(gòu)


Reactor模式的角色構(gòu)成(Reactor模式一共有5中角色構(gòu)成):

  • Handle(句柄或描述符,在Windows下稱為句柄,在Linux下稱為描述符):本質(zhì)上表示一種資源(比如說文件描述符,或是針對(duì)網(wǎng)絡(luò)編程中的socket描述符),是由操作系統(tǒng)提供的;該資源用于表示一個(gè)個(gè)的事件,事件既可以來自于外部,也可以來自于內(nèi)部;外部事件比如說客戶端的連接請(qǐng)求,客戶端發(fā)送過來的數(shù)據(jù)等;內(nèi)部事件比如說操作系統(tǒng)產(chǎn)生的定時(shí)事件等。它本質(zhì)上就是一個(gè)文件描述符,Handle是事件產(chǎn)生的發(fā)源地。
  • Synchronous Event Demultiplexer(同步事件分離器):它本身是一個(gè)系統(tǒng)調(diào)用,用于等待事件的發(fā)生(事件可能是一個(gè),也可能是多個(gè))。調(diào)用方在調(diào)用它的時(shí)候會(huì)被阻塞,一直阻塞到同步事件分離器上有事件產(chǎn)生為止。對(duì)于Linux來說,同步事件分離器指的就是常用的I/O多路復(fù)用機(jī)制,比如說select、poll、epoll等。在Java NIO領(lǐng)域中,同步事件分離器對(duì)應(yīng)的組件就是Selector;對(duì)應(yīng)的阻塞方法就是select方法。
  • Event Handler(事件處理器):本身由多個(gè)回調(diào)方法構(gòu)成,這些回調(diào)方法構(gòu)成了與應(yīng)用相關(guān)的對(duì)于某個(gè)事件的反饋機(jī)制。在Java NIO領(lǐng)域中并沒有提供事件處理器機(jī)制讓我們調(diào)用或去進(jìn)行回調(diào),是由我們自己編寫代碼完成的。Netty相比于Java NIO來說,在事件處理器這個(gè)角色上進(jìn)行了一個(gè)升級(jí),它為我們開發(fā)者提供了大量的回調(diào)方法,供我們?cè)谔囟ㄊ录a(chǎn)生時(shí)實(shí)現(xiàn)相應(yīng)的回調(diào)方法進(jìn)行業(yè)務(wù)邏輯的處理,即,ChannelHandler。ChannelHandler中的方法對(duì)應(yīng)的都是一個(gè)個(gè)事件的回調(diào)。
  • Concrete Event Handler(具體事件處理器):是事件處理器的實(shí)現(xiàn)。它本身實(shí)現(xiàn)了事件處理器所提供的各種回調(diào)方法,從而實(shí)現(xiàn)了特定于業(yè)務(wù)的邏輯。它本質(zhì)上就是我們所編寫的一個(gè)個(gè)的處理器實(shí)現(xiàn)。
  • Initiation Dispatcher(初始分發(fā)器):實(shí)際上就是Reactor角色。它本身定義了一些規(guī)范,這些規(guī)范用于控制事件的調(diào)度方式,同時(shí)又提供了應(yīng)用進(jìn)行事件處理器的注冊(cè)、刪除等設(shè)施。它本身是整個(gè)事件處理器的核心所在,Initiation Dispatcher會(huì)通過Synchronous Event Demultiplexer來等待事件的發(fā)生。一旦事件發(fā)生,Initiation Dispatcher首先會(huì)分離出每一個(gè)事件,然后調(diào)用事件處理器,最后調(diào)用相關(guān)的回調(diào)方法來處理這些事件。Netty中ChannelHandler里的一個(gè)個(gè)回調(diào)方法都是由bossGroup或workGroup中的某個(gè)EventLoop來調(diào)用的。

Reactor模式流程

① 初始化Initiation Dispatcher,然后將若干個(gè)Concrete Event Handler注冊(cè)到Initiation Dispatcher中。當(dāng)應(yīng)用向Initiation Dispatcher注冊(cè)Concrete Event Handler時(shí),會(huì)在注冊(cè)的同時(shí)指定感興趣的事件,即,應(yīng)用會(huì)標(biāo)識(shí)出該事件處理器希望Initiation Dispatcher在某些事件發(fā)生時(shí)向其發(fā)出通知,事件通過Handle來標(biāo)識(shí),而Concrete Event Handler又持有該Handle。這樣,事件 ————> Handle ————> Concrete Event Handler 就關(guān)聯(lián)起來了。
② Initiation Dispatcher 會(huì)要求每個(gè)事件處理器向其傳遞內(nèi)部的Handle。該Handle向操作系統(tǒng)標(biāo)識(shí)了事件處理器。
③ 當(dāng)所有的Concrete Event Handler都注冊(cè)完畢后,應(yīng)用會(huì)調(diào)用handle_events方法來啟動(dòng)Initiation Dispatcher的事件循環(huán)。這是,Initiation Dispatcher會(huì)將每個(gè)注冊(cè)的Concrete Event Handler的Handle合并起來,并使用Synchronous Event Demultiplexer(同步事件分離器)同步阻塞的等待事件的發(fā)生。比如說,TCP協(xié)議層會(huì)使用select同步事件分離器操作來等待客戶端發(fā)送的數(shù)據(jù)到達(dá)連接的socket handler上。
比如,在Java中通過Selector的select()方法來實(shí)現(xiàn)這個(gè)同步阻塞等待事件發(fā)生的操作。在Linux操作系統(tǒng)下,select()的實(shí)現(xiàn)中 a)會(huì)將已經(jīng)注冊(cè)到Initiation Dispatcher的事件調(diào)用epollCtl(epfd, opcode, fd, events)注冊(cè)到linux系統(tǒng)中,這里fd表示Handle,events表示我們所感興趣的Handle的事件;b)通過調(diào)用epollWait方法同步阻塞的等待已經(jīng)注冊(cè)的事件的發(fā)生。不同事件源上的事件可能同時(shí)發(fā)生,一旦有事件被觸發(fā)了,epollWait方法就會(huì)返回;c)最后通過發(fā)生的事件找到相關(guān)聯(lián)的SelectorKeyImpl對(duì)象,并設(shè)置其發(fā)生的事件為就緒狀態(tài),然后將SelectorKeyImpl放入selectedSet中。這樣一來我們就可以通過Selector.selectedKeys()方法得到事件就緒的SelectorKeyImpl集合了。
④ 當(dāng)與某個(gè)事件源對(duì)應(yīng)的Handle變?yōu)閞eady狀態(tài)時(shí)(比如說,TCP socket變?yōu)榈却x狀態(tài)時(shí)),Synchronous Event Demultiplexer就會(huì)通知Initiation Dispatcher。
⑤ Initiation Dispatcher會(huì)觸發(fā)事件處理器的回調(diào)方法,從而響應(yīng)這個(gè)處于ready狀態(tài)的Handle。當(dāng)事件發(fā)生時(shí),Initiation Dispatcher會(huì)將被事件源激活的Handle作為『key』來尋找并分發(fā)恰當(dāng)?shù)氖录幚砥骰卣{(diào)方法。
⑥ Initiation Dispatcher會(huì)回調(diào)事件處理器的handle_event(type)回調(diào)方法來執(zhí)行特定于應(yīng)用的功能(開發(fā)者自己所編寫的功能),從而相應(yīng)這個(gè)事件。所發(fā)生的事件類型可以作為該方法參數(shù)并被該方法內(nèi)部使用來執(zhí)行額外的特定于服務(wù)的分離與分發(fā)。

Reactor模式的實(shí)現(xiàn)方式

單線程Reactor模式

流程:
① 服務(wù)器端的Reactor是一個(gè)線程對(duì)象,該線程會(huì)啟動(dòng)事件循環(huán),并使用Selector來實(shí)現(xiàn)IO的多路復(fù)用。注冊(cè)一個(gè)Acceptor事件處理器到Reactor中,Acceptor事件處理器所關(guān)注的事件是ACCEPT事件,這樣Reactor會(huì)監(jiān)聽客戶端向服務(wù)器端發(fā)起的連接請(qǐng)求事件(ACCEPT事件)。
② 客戶端向服務(wù)器端發(fā)起一個(gè)連接請(qǐng)求,Reactor監(jiān)聽到了該ACCEPT事件的發(fā)生并將該ACCEPT事件派發(fā)給相應(yīng)的Acceptor處理器來進(jìn)行處理。Acceptor處理器通過accept()方法得到與這個(gè)客戶端對(duì)應(yīng)的連接(SocketChannel),然后將該連接所關(guān)注的READ事件以及對(duì)應(yīng)的READ事件處理器注冊(cè)到Reactor中,這樣一來Reactor就會(huì)監(jiān)聽該連接的READ事件了?;蛘弋?dāng)你需要向客戶端發(fā)送數(shù)據(jù)時(shí),就向Reactor注冊(cè)該連接的WRITE事件和其處理器。
③ 當(dāng)Reactor監(jiān)聽到有讀或者寫事件發(fā)生時(shí),將相關(guān)的事件派發(fā)給對(duì)應(yīng)的處理器進(jìn)行處理。比如,讀處理器會(huì)通過SocketChannel的read()方法讀取數(shù)據(jù),此時(shí)read()操作可以直接讀取到數(shù)據(jù),而不會(huì)堵塞與等待可讀的數(shù)據(jù)到來。
④ 每當(dāng)處理完所有就緒的感興趣的I/O事件后,Reactor線程會(huì)再次執(zhí)行select()阻塞等待新的事件就緒并將其分派給對(duì)應(yīng)處理器進(jìn)行處理。

注意,Reactor的單線程模式的單線程主要是針對(duì)于I/O操作而言,也就是所以的I/O的accept()、read()、write()以及connect()操作都在一個(gè)線程上完成的。

但在目前的單線程Reactor模式中,不僅I/O操作在該Reactor線程上,連非I/O的業(yè)務(wù)操作也在該線程上進(jìn)行處理了,這可能會(huì)大大延遲I/O請(qǐng)求的響應(yīng)。所以我們應(yīng)該將非I/O的業(yè)務(wù)邏輯操作從Reactor線程上卸載,以此來加速Reactor線程對(duì)I/O請(qǐng)求的響應(yīng)。

改進(jìn):使用工作者線程池

與單線程Reactor模式不同的是,添加了一個(gè)工作者線程池,并將非I/O操作從Reactor線程中移出轉(zhuǎn)交給工作者線程池來執(zhí)行。這樣能夠提高Reactor線程的I/O響應(yīng),不至于因?yàn)橐恍┖臅r(shí)的業(yè)務(wù)邏輯而延遲對(duì)后面I/O請(qǐng)求的處理。

使用線程池的優(yōu)勢(shì):
① 通過重用現(xiàn)有的線程而不是創(chuàng)建新線程,可以在處理多個(gè)請(qǐng)求時(shí)分?jǐn)傇诰€程創(chuàng)建和銷毀過程產(chǎn)生的巨大開銷。
② 另一個(gè)額外的好處是,當(dāng)請(qǐng)求到達(dá)時(shí),工作線程通常已經(jīng)存在,因此不會(huì)由于等待創(chuàng)建線程而延遲任務(wù)的執(zhí)行,從而提高了響應(yīng)性。
③ 通過適當(dāng)調(diào)整線程池的大小,可以創(chuàng)建足夠多的線程以便使處理器保持忙碌狀態(tài)。同時(shí)還可以防止過多線程相互競(jìng)爭(zhēng)資源而使應(yīng)用程序耗盡內(nèi)存或失敗。

注意,在上圖的改進(jìn)的版本中,所以的I/O操作依舊由一個(gè)Reactor來完成,包括I/O的accept()、read()、write()以及connect()操作。
對(duì)于一些小容量應(yīng)用場(chǎng)景,可以使用單線程模型。但是對(duì)于高負(fù)載、大并發(fā)或大數(shù)據(jù)量的應(yīng)用場(chǎng)景卻不合適,主要原因如下:
① 一個(gè)NIO線程同時(shí)處理成百上千的鏈路,性能上無法支撐,即便NIO線程的CPU負(fù)荷達(dá)到100%,也無法滿足海量消息的讀取和發(fā)送;
② 當(dāng)NIO線程負(fù)載過重之后,處理速度將變慢,這會(huì)導(dǎo)致大量客戶端連接超時(shí),超時(shí)之后往往會(huì)進(jìn)行重發(fā),這更加重了NIO線程的負(fù)載,最終會(huì)導(dǎo)致大量消息積壓和處理超時(shí),成為系統(tǒng)的性能瓶頸;

多Reactor線程模式

Reactor線程池中的每一Reactor線程都會(huì)有自己的Selector、線程和分發(fā)的事件循環(huán)邏輯。
mainReactor可以只有一個(gè),但subReactor一般會(huì)有多個(gè)。mainReactor線程主要負(fù)責(zé)接收客戶端的連接請(qǐng)求,然后將接收到的SocketChannel傳遞給subReactor,由subReactor來完成和客戶端的通信。

流程:
① 注冊(cè)一個(gè)Acceptor事件處理器到mainReactor中,Acceptor事件處理器所關(guān)注的事件是ACCEPT事件,這樣mainReactor會(huì)監(jiān)聽客戶端向服務(wù)器端發(fā)起的連接請(qǐng)求事件(ACCEPT事件)。啟動(dòng)mainReactor的事件循環(huán)。
② 客戶端向服務(wù)器端發(fā)起一個(gè)連接請(qǐng)求,mainReactor監(jiān)聽到了該ACCEPT事件并將該ACCEPT事件派發(fā)給Acceptor處理器來進(jìn)行處理。Acceptor處理器通過accept()方法得到與這個(gè)客戶端對(duì)應(yīng)的連接(SocketChannel),然后將這個(gè)SocketChannel傳遞給subReactor線程池。
③ subReactor線程池分配一個(gè)subReactor線程給這個(gè)SocketChannel,即,將SocketChannel關(guān)注的READ事件以及對(duì)應(yīng)的READ事件處理器注冊(cè)到subReactor線程中。當(dāng)然你也注冊(cè)WRITE事件以及WRITE事件處理器到subReactor線程中以完成I/O寫操作。Reactor線程池中的每一Reactor線程都會(huì)有自己的Selector、線程和分發(fā)的循環(huán)邏輯。
④ 當(dāng)有I/O事件就緒時(shí),相關(guān)的subReactor就將事件派發(fā)給響應(yīng)的處理器處理。注意,這里subReactor線程只負(fù)責(zé)完成I/O的read()操作,在讀取到數(shù)據(jù)后將業(yè)務(wù)邏輯的處理放入到線程池中完成,若完成業(yè)務(wù)邏輯后需要返回?cái)?shù)據(jù)給客戶端,則相關(guān)的I/O的write操作還是會(huì)被提交回subReactor線程來完成。

注意,所以的I/O操作(包括,I/O的accept()、read()、write()以及connect()操作)依舊還是在Reactor線程(mainReactor線程 或 subReactor線程)中完成的。Thread Pool(線程池)僅用來處理非I/O操作的邏輯。

多Reactor線程模式將“接受客戶端的連接請(qǐng)求”和“與該客戶端的通信”分在了兩個(gè)Reactor線程來完成。mainReactor完成接收客戶端連接請(qǐng)求的操作,它不負(fù)責(zé)與客戶端的通信,而是將建立好的連接轉(zhuǎn)交給subReactor線程來完成與客戶端的通信,這樣一來就不會(huì)因?yàn)閞ead()數(shù)據(jù)量太大而導(dǎo)致后面的客戶端連接請(qǐng)求得不到即時(shí)處理的情況。并且多Reactor線程模式在海量的客戶端并發(fā)請(qǐng)求的情況下,還可以通過實(shí)現(xiàn)subReactor線程池來將海量的連接分發(fā)給多個(gè)subReactor線程,在多核的操作系統(tǒng)中這能大大提升應(yīng)用的負(fù)載和吞吐量。

Netty 與 Reactor模式

Netty的線程模式就是一個(gè)實(shí)現(xiàn)了Reactor模式的經(jīng)典模式。

  • 結(jié)構(gòu)對(duì)應(yīng):
    NioEventLoop ———— Initiation Dispatcher
    Synchronous EventDemultiplexer ———— Selector
    Evnet Handler ———— ChannelHandler
    ConcreteEventHandler ———— 具體的ChannelHandler的實(shí)現(xiàn)

  • 模式對(duì)應(yīng):
    Netty服務(wù)端使用了“多Reactor線程模式”
    mainReactor ———— bossGroup(NioEventLoopGroup) 中的某個(gè)NioEventLoop
    subReactor ———— workerGroup(NioEventLoopGroup) 中的某個(gè)NioEventLoop
    acceptor ———— ServerBootstrapAcceptor
    ThreadPool ———— 用戶自定義線程池

  • 流程:
    ① 當(dāng)服務(wù)器程序啟動(dòng)時(shí),會(huì)配置ChannelPipeline,ChannelPipeline中是一個(gè)ChannelHandler鏈,所有的事件發(fā)生時(shí)都會(huì)觸發(fā)Channelhandler中的某個(gè)方法,這個(gè)事件會(huì)在ChannelPipeline中的ChannelHandler鏈里傳播。然后,從bossGroup事件循環(huán)池中獲取一個(gè)NioEventLoop來現(xiàn)實(shí)服務(wù)端程序綁定本地端口的操作,將對(duì)應(yīng)的ServerSocketChannel注冊(cè)到該NioEventLoop中的Selector上,并注冊(cè)ACCEPT事件為ServerSocketChannel所感興趣的事件。
    ② NioEventLoop事件循環(huán)啟動(dòng),此時(shí)開始監(jiān)聽客戶端的連接請(qǐng)求。
    ③ 當(dāng)有客戶端向服務(wù)器端發(fā)起連接請(qǐng)求時(shí),NioEventLoop的事件循環(huán)監(jiān)聽到該ACCEPT事件,Netty底層會(huì)接收這個(gè)連接,通過accept()方法得到與這個(gè)客戶端的連接(SocketChannel),然后觸發(fā)ChannelRead事件(即,ChannelHandler中的channelRead方法會(huì)得到回調(diào)),該事件會(huì)在ChannelPipeline中的ChannelHandler鏈中執(zhí)行、傳播。
    ④ ServerBootstrapAcceptor的readChannel方法會(huì)該SocketChannel(客戶端的連接)注冊(cè)到workerGroup(NioEventLoopGroup) 中的某個(gè)NioEventLoop的Selector上,并注冊(cè)READ事件為SocketChannel所感興趣的事件。啟動(dòng)SocketChannel所在NioEventLoop的事件循環(huán),接下來就可以開始客戶端和服務(wù)器端的通信了。

后記

本文主要對(duì)Reactor模式進(jìn)行詳細(xì)的解析,Netty中正是應(yīng)用Reactor模式來實(shí)現(xiàn)異步事件驅(qū)動(dòng)網(wǎng)絡(luò)應(yīng)用框架的,所以對(duì)于Reactor模式的掌握在Netty的學(xué)習(xí)是至關(guān)重要的。
若文章有任何錯(cuò)誤,望大家不吝指教:)

參考

圣思園《精通并發(fā)與Netty》
http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf
https://tech.meituan.com/nio.html
http://www.infoq.com/cn/articles/netty-threading-model
http://www.dre.vanderbilt.edu/~schmidt/PDF/reactor-siemens.pdf
Netty 權(quán)威指南
Java并發(fā)編程實(shí)戰(zhàn)

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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