Netty學習筆記-入門版

Netty學習筆記

前言

本文簡單介紹下netty的基本原理,I/O模型,Reactor線程模型以及架構設計等相關知識點。html

什麼是Netty

Netty是由JBOSS提供的一個Java開源框架。Netty提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。java

Netty 是一個基於NIO的客戶、服務器端編程框架,使用Netty 能夠確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶,服務端應用。Netty至關簡化和流線化了網絡應用的編程開發過程,例如,TCP和UDP的socket服務開發。

Netty是由NIO演進而來,使用過NIO編程的用戶就知道NIO編程很是繁重,Netty是可以能跟好的使用NIOreact

IO基礎

概念說明

IO簡單介紹

IO在計算機中指Input/Output,也就是輸入和輸出。因爲程序和運行時數據是在內存中駐留,由CPU這個超快的計算核心來執行,涉及到數據交換的地方,一般是磁盤、網絡等,就須要IO接口。linux

好比你打開瀏覽器,訪問新浪首頁,瀏覽器這個程序就須要經過網絡IO獲取新浪的網頁。瀏覽器首先會發送數據給新浪服務器,告訴它我想要首頁的HTML,這個動做是往外發數據,叫Output,隨後新浪服務器把網頁發過來,這個動做是從外面接收數據,叫Input。因此,一般,程序完成IO操做會有Input和Output兩個數據流。固然也有隻用一個的狀況,好比,從磁盤讀取文件到內存,就只有Input操做,反過來,把數據寫到磁盤文件裏,就只是一個Output操做。web

用戶空間與內核空間

如今操做系統都是採用虛擬存儲器,那麼對32位操做系統而言,它的尋址空間(虛擬存儲空間)爲4G(2的32次方)操做系統的核心是內核,獨立於普通的應用程序,能夠訪問受保護的內存空間,也有訪問底層硬件設備(例如負責磁盤IO的設備)的全部權限爲了保證用戶進程不能直接操做內核(kernel),保證內核的安全,操做系統將虛擬空間劃分爲兩部分,一部分爲內核空間,一部分爲用戶空間。ajax

針對linux操做系統而言,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱爲內核空間,而將較低的3G字節(從虛擬地址0×00000000到0xBFFFFFFF),供各個進程使用,稱爲用戶空間。編程

進程(Process)

是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎segmentfault

在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎。程序是指令、數據及其組織形式的描述,進程是程序的實體。數組

線程(thread)

是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務。瀏覽器

程序和進程

1個程序能夠對應多個進程,但1個進程只能對應1個程序。

說白了就是,一個程序能夠重複運行,開幾個窗口,好比網遊的「雙開」,一個進程能夠對應多個程序就是一個DLL文件可一被多個程序運用,好比DirectX9的動態連接庫,就是,許多遊戲都要有它才能運行。

咱們簡單總結下:

進程包含線程。

進程:指在系統中正在運行的一個應用程序;程序一旦運行就是進程;進程——資源分配的最小單位。

線程:系統分配處理器時間資源的基本單元,或者說進程以內獨立執行的一個單元執行流。線程——程序執行的最小單位。

進程切換

爲了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,並恢復之前掛起的某個進程的執行。這種行爲被稱爲進程切換。所以能夠說,任何進程都是在操做系統內核的支持下運行的,是與內核緊密相關的。

從一個進程的運行轉到另外一個進程上運行,這個過程當中通過下面這些變化:

  1. 保存處理機上下文,包括程序計數器和其餘寄存器。
  2. 更新PCB信息。
  3. 把進程的PCB移入相應的隊列,如就緒、在某事件阻塞等隊列。
  4. 選擇另外一個進程執行,並更新其PCB。
  5. 更新內存管理的數據結構。
  6. 恢復處理機上下文。

注:總而言之就是很耗資源

進程阻塞

正在執行的進程,因爲期待的某些事件未發生,如請求系統資源失敗、等待某種操做的完成、新數據還沒有到達或無新工做作等,則由系統自動執行阻塞原語(Block),使本身由運行狀態變爲阻塞狀態。可見,進程的阻塞是進程自身的一種主動行爲,也所以只有處於運行態的進程(得到CPU),纔可能將其轉爲阻塞狀態。當進程進入阻塞狀態,是不佔用CPU資源的

文件描述符

簡單理解:一個指向文件自己的指針(由系統所管理的引用標識,該標識能夠被系統從新定位到一個內存地址上,間接訪問對象 ),值是非負整數。

文件描述符(File descriptor,簡稱fd)是計算機科學中的一個術語,是一個用於表述指向文件的引用的抽象化概念。

文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核爲每個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者建立一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫每每會圍繞着文件描述符展開。可是文件描述符這一律念每每只適用於UNIX、Linux這樣的操做系統。

文件句柄

Windows下的概念。句柄是Windows下各類對象的標識符,好比文件、資源、菜單、光標等等。文件句柄和文件描述符相似,它也是一個非負整數,也用於定位文件數據在內存中的位置。

緩存IO

大多數文件系統的默認IO都是緩存IO。過程是:數據先被拷貝到操做系統的內核緩衝區(頁緩存 page cache)中,而後再拷貝到應用程序的地址空間。

Linux 網絡I/O模型

同步、異步、阻塞、非阻塞的概念

同步

所謂同步,發起一個功能調用的時候,在沒有獲得結果以前,該調用不返回,也就是必須一件事一件事的作,等前一件作完了,才能作下一件。

main函數

int main(){

add();

sout();

}

int add(){

return 1+1;

}

異步

main函數

int main(){

ajax();

sout();

}



int ajax(){

return 1+1;

}

調用發出後,調用者不能馬上獲得結果,而是實際處理這個調用的函數完成以後,經過狀態、通知和回調來通知調用者

好比ajax:

​ 請求經過事件觸發->服務器處理(這是瀏覽器仍然能夠做其餘事情)->處理完畢

(在服務器處理的時候,客戶端還能夠幹其餘的事)

阻塞

指調用結果返回以前,當前線程會被掛起(CPU不給線程分配時間片),函數只能在獲得結果以後纔會返回。

(阻塞調用和同步調用的區別)同步調用的時候,當前線程仍然多是激活的,只是在邏輯上當前函數沒有返回。例如:在Socket中調用recv函數,若是緩衝區沒有數據,這個函數會一直等待,知道數據返回。而在此時,這個線程仍是能夠處理其餘消息的。

非阻塞

非阻塞調用指在不能馬上獲得結果以前,該調用不會阻塞當前線程。

總結

同步是指A調用了B函數,B函數須要等處理完事情纔會給A返回一個結果。A拿到結果繼續執行。

異步是指A調用了B函數,A的任務就完成了,去繼續執行別的事了,等B處理完了事情,纔會通知A。

阻塞是指,A調用了B函數,在B沒有返回結果的時候,A線程被CPU掛起,不能執行任何操做(這個線程不會被分配時間片)

非阻塞是指,A調用了B函數,A不用一直等待B返回結果,能夠先去幹別的事。

舉個例子

老張愛喝茶,廢話不說,煮開水。 出場人物:老張,水壺兩把(普通水壺,簡稱水壺;會響的水壺,簡稱響水壺)。

  1. 老張把水壺放到火上,立等水開。(同步阻塞) 老張以爲本身有點傻
  2. 老張把水壺放到火上,去客廳看電視,時不時去廚房看看水開沒有。(同步非阻塞) 老張仍是以爲本身有點傻,因而變高端了,買了把會響笛的那種水壺。水開以後,能大聲發出嘀~~~~的噪音。
  3. 老張把響水壺放到火上,立等水開。(異步阻塞) 老張以爲這樣傻等意義不大
  4. 老張把響水壺放到火上,去客廳看電視,水壺響以前再也不去看它了,響了再去拿壺。(異步非阻塞) 老張以爲本身聰明瞭。

所謂同步異步,只是對於水壺而言。

普通水壺,同步;響水壺,異步。雖然都能幹活,但響水壺能夠在本身完工以後,提示老張水開了。這是普通水壺所不能及的。同步只能讓調用者去輪詢本身(狀況2中),形成老張效率的低下。

所謂阻塞非阻塞,僅僅對於老張而言。立等的老張,阻塞;看電視的老張,非阻塞。狀況1和狀況3中老張就是阻塞的,媳婦喊他都不知道。雖然3中響水壺是異步的,可對於立等的老張沒有太大的意義。因此通常異步是配合非阻塞使用的,這樣才能發揮異步的效用。

I/O模型

recvfrom函數用於從(已鏈接)套接口上接收數據,並捕獲數據發送源的地址。
本函數用於從(已鏈接)套接口上接收數據,並捕獲數據發送源的地址。
(簡單理解就是客戶端等待服務端給數據的函數)

舉個例子,其中的角色,客人(小明)對應內核線程,服務員對應的是用戶線程。如今大黃在南亭新開了一家黃燜雞,小明(客人)以爲很新鮮,準備喊上幾個基友去南亭搓一頓黃燜雞。

小明到店裏了,若是小明成功點餐則須要通過兩個步驟,第一步是思考要點什麼吃的,第二步是跟服務員說要吃什麼。

該模型的內核線程分爲兩個階段

  • 數據準備階段:(等待點餐)未阻塞,當數據準備完成以後,會主動的通知用戶進程數據已經準備完成,對用戶進程作一個回調。
  • 數據拷貝階段:(進行點餐)阻塞用戶進程,等待數據拷貝。
阻塞 I/O(blocking IO)

如今黃燜雞的老闆大黃給每一個客人都配一個服務員,只要有一個客人來的話,就在旁邊等客人思考吃什麼而且進行點餐。只要客人尚未點餐完畢,對應的這個服務員就不能離開去作別的事情。

映射到Linux操做系統中,這就是阻塞的IO模型。在linux中,默認狀況下全部的socket都是blocking,一個典型的讀操做流程大概是這樣:

用戶線程(服務員) 內核線程(客人)

當用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的兩個階段:

  1. 準備數據(對於網絡IO來講,不少時候數據在一開始尚未到達。好比,尚未收到一個完整的UDP包。這個時候kernel就要等待足夠的數據到來)。這個過程須要等待,也就是說數據被拷貝到操做系統內核的緩衝區中是須要一個過程的。而在用戶進程這邊,整個進程會被阻塞(固然,是進程本身選擇的阻塞)。
  2. 當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶內存,而後kernel返回結果,用戶進程才解除block的狀態,從新運行起來。

因此,blocking IO的特色就是在IO執行的兩個階段(等待IO(準備IO)和執行IO)都被block了。

非阻塞 I/O(nonblocking IO)

如今隨着有些大學生月初拿到零花錢,開始浪了,黃燜雞的生意也變得愈來愈火爆了,來吃飯的客人愈來愈多,要配的服務員也愈來愈多,黃燜雞的老闆大黃心想這不對勁啊,要是忽然同時來100個客人,就要有100個服務員,確定鉅虧啊,得想個法子提升效率。這時候老闆大黃想到,在客人想點什麼吃的時候,服務員徹底能夠去作別的事情,例如去給別的桌的客人點餐,只要偶爾過來問下客人是否要點餐了,一旦發現客人須要點餐了,就開始點餐。

映射到Linux操做系統中,這就是非阻塞的IO模型。linux下,能夠經過設置socket使其變爲non-blocking。當對一個non-blocking socket執行讀操做時,流程是這個樣子:

  1. 當用戶進程發出read操做時,若是kernel中的數據尚未準備好,那麼它並不會block用戶進程,而是馬上返回一個error。從用戶進程角度講 ,它發起一個read操做後,並不須要等待,而是立刻就獲得了一個結果。用戶進程判斷結果是一個error時,它就知道數據尚未準備好,因而它能夠再次發送read操做。
  2. 一旦kernel中的數據準備好了,而且又再次收到了用戶進程的system call,那麼它立刻就將數據拷貝到了用戶內存,而後返回。

因此,nonblocking IO的特色是用戶進程須要不斷的主動詢問kernel數據好了沒有。

信號驅動I/O( signal driven IO )

過了一段時間,客人們好多都反應,大家服務員太煩人了,成天問我是否是能夠點餐了,客人說咱們乾脆要點餐的時候就叫服務員過來好了,老闆大黃心想這樣也不錯,能提升餐廳的運行效率,賺多點錢,便答應了(這時候的服務員還在客人旁邊傻乎乎的站着,只等着客人喊他點餐)。

映射到Linux操做系統中,這就是信號驅動I/O。當數據報準備好的時候,內核會嚮應用程序發送一個信號,進程對信號進行捕捉,而且調用信號處理函數來獲取數據報。

img

I/O 多路複用( IO multiplexing)

老闆大黃巡查店內狀況,看到了服務員大多都傻乎乎的站着等客人喊他點餐,做爲資本家的大黃,固然是要充分利用勞動力的。因此老闆給服務員們開了個會,安排他們一我的負責一個區域(多個客人)的客人點餐需求,等到客人喊他點餐時,就過去點餐。

映射到Linux操做系統中,這就是I/O 多路複用。IO multiplexing就是咱們說的select,poll,epoll,有些地方也稱這種IO方式爲event driven IO。select/epoll的好處就在於單個process就能夠同時處理多個網絡鏈接的IO。它的基本原理就是select,poll,epoll這個function會不斷的輪詢所負責的全部socket,當某個socket有數據到達了,就通知用戶進程

當用戶進程調用了select,那麼整個進程會被block,而同時,kernel會「監視」全部select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操做,將數據從kernel拷貝到用戶進程。

因此,I/O 多路複用的特色是經過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就能夠返回。

這個圖和blocking IO的圖其實並無太大的不一樣,事實上,還更差一些。由於這裏須要使用兩個system call (select 和 recvfrom),而blocking IO只調用了一個system call (recvfrom)。可是,用select的優點在於它能夠同時處理多個connection。

因此,若是處理的鏈接數不是很高的話,使用select/epoll的web server不必定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。select/epoll的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。)

在IO multiplexing Model中,實際中,對於每個socket,通常都設置成爲non-blocking,可是,如上圖所示,整個用戶的process實際上是一直被block的。只不過process是被select這個函數block,而不是被socket IO給block。

異步 I/O(asynchronous IO)

後來,隨着黃燜雞的味道你們都比較喜好,口碑逐漸創建了起來,生意愈加地火爆,經常座無虛席,大黃也開始夢想着多久能實現一個小目標。大學城裏有些創業團隊看到了黃燜雞的火爆程度,看到服務員有不少時候是在給客人點餐,他們結合本身的專業知識,跟老闆說給老闆開發個手機點餐系統,客人經過手機就能點餐,服務員只須要查看系統的點餐狀況進行上菜就好,老闆大黃一看,秒啊,急忙答應了,經過點餐系統,餐廳運行效率更高了,接待的客人也愈來愈多,離一個小目標的夢想也愈來愈近了。

映射到Linux操做系統中,這就是異步 I/O。Linux下的asynchronous IO其實用得不多。先看一下它的流程:

用戶進程發起read操做以後,馬上就能夠開始去作其它的事。而另外一方面,從kernel的角度,當它受到一個asynchronous read以後,首先它會馬上返回,因此不會對用戶進程產生任何block。而後,kernel會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都完成以後,kernel會給用戶進程發送一個signal,告訴它read操做完成了。

用戶進程只須要知道內核線程處理的結果

5中I/O模型對比

IO多路複用的三種機制

select

數組(長度1024)存儲全部的fd

說的通俗一點就是各個客戶端鏈接的文件描述符也就是套接字,都被放到了一個集合中,調用select函數以後會一直監視這些文件描述符中有哪些可讀,若是有可讀的描述符那麼咱們的工做進程就去讀取資源。

select 在一個進程內能夠維持最多 1024 個鏈接

poll

鏈表存儲全部的fd

poll 和 select 的實現很是相似,本質上的區別就是存放 fd 集合的數據結構不同。poll 在select基礎上作了增強,能夠維持任意數量的鏈接

但 select 和 poll 方式有一個很大的問題就是,咱們不難看出來 select和poll 是經過輪循的方式來查找是否可讀或者可寫,打個比方,若是同時有100萬個鏈接都沒有斷開,而只有一個客戶端發送了數據,因此這裏它仍是須要循環這麼屢次,形成資源浪費。

epoll

鏈表存儲ready的fd

不須要遍歷所有fd去找ready的,其所有的fd放在一個紅黑樹中加以維護。

epoll 是 select 和 poll 的加強版,epoll 同 poll 同樣,文件描述符數量無限制

epoll是基於內核的反射機制,在有活躍的 socket 時,系統會調用咱們提早設置的回調函數。而 poll 和 select 都是遍歷。

可是也並非全部狀況下 epoll 都比 select/poll 好,好比在以下場景:

在大多數客戶端都很活躍的狀況下,系統會把全部的回調函數都喚醒,因此會致使負載較高。既然要處理這麼多的鏈接,那倒不如 select 遍歷簡單有效。

NIO入門

引言

在Java中提供了三種IO模型:BIO、NIO、AIO,模型的選擇決定了程序通訊的性能。

使用場景

  • BIO
    BIO適用於鏈接數比較小的應用,這種IO模型對服務器資源要求比較高。

  • NIO
    NIO適用於鏈接數目多、鏈接時間短的應用,好比聊天、彈幕、服務器間通信等應用。

  • AIO
    AIO適用於鏈接數目多、鏈接時間長的應用,好比相冊服務器。

BIO

同步阻塞式

無腦建立線程

僞異步I/O阻塞式

改用線程池

NIO

同步非阻塞模型,服務器端用一個線程處理多個鏈接,客戶端發送的鏈接請求會註冊到多路複用器上,多路複用器輪詢到鏈接有IO請求就進行處理:

NIO的非阻塞模式,使得一個線程從某通道發送請求或者讀取數據時,若是目前沒有可用的數據,不會使線程阻塞,在數據可讀以前,該線程能夠作其餘的事情。

NIO有三大核心部分:

  • Channel(通道)
  • Buffer(緩衝區)
  • Selector(選擇器)


由圖可知:

  • 每一個Channel對應一個Buffer。
  • Selector對應一個線程,一個線程對應多個Channel。
  • Selector會根據不一樣的事件,在各個通道上切換。
  • Buffer是內存塊,底層是數據。

緩衝區(Buffer)

本質是能夠讀寫數據的內存塊,Channel讀取或者寫入的數據必須經過Buffer:

java.nio.Buffer抽象類的屬性:

複製代碼

// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;

Buffer-屬性.png

讀寫交換要使用flip方法。

通道(Channel)

通道是雙向的,能夠讀操做、也能夠寫操做。
java.nio.channels.Channel接口的經常使用實現類:

FileChannel用於文件的數據讀寫,DatagramChannel用於UDP的數據讀寫,ServerSocketChannel和SocketChannel用於TCP的數據讀寫。

選擇器(Selector)

Selector選擇器使用一個線程來維護。多個Channel會以事件的方式註冊到同一個Selector,當有事件發生時,Selector會獲取事件,而後針對每一個事件進行響應的處理。這樣就沒必要爲每一個鏈接建立一個線程,不用維護多線程,也不會有多線程之間的上下文切換致使的系統的開銷。
Selector示意圖:

AIO

異步非阻塞模型,AIO引入異步通道的概念,使用了Proactor,只有有效的請求才啓動線程,特色是先由操做系統完成後,才通知服務器端程序啓動線程去處理,通常適用於鏈接數較多且鏈接時間較長的應用。

對比

Reactor線程模型

Reactor線程模型是基於同步非阻塞IO實現的。對於異步非阻塞IO的實現是Proactor模型。

Netty就是基於Reactor線程模型開發的,咱們今天來簡單分析下:

Reactor模型中的三種角色及含義:
    Reactor:將I/O事件分配給對應的handler。
    Acceptor:處理客戶端新鏈接,並分派請求處處理器鏈中。
    Handlers:執行非阻塞讀寫任務。

Reactor經常使用的線程模型有三種

Reactor單線程模型

單線程模型簡圖

單線程模型就是指全部的I/O操做都是在一個線程中處理完成,NIO的線程須要接受客戶端的Tcp鏈接,而且向客戶端發送Tcp鏈接,讀取通訊兩端的請求或應答,發送請求和應答。

單線程模型詳細圖解

大體瞭解了後,讓咱們看下這個詳細流程,當客戶端發起鏈接,Acceptor負責接收客戶端的Tcp請求,鏈路創建成功後,經過Dispatcher將對應的ByteBuffer派發到指定的Hnadler上進行消息解碼,用戶Handler經過NIO線程將消息發送給客戶端。單線程模型其實就是Acceptor的處理和Handler的處理都處在同一個線程中,當其中的一個Hnadler阻塞時,會致使其它的client和handler沒法執行,甚至整個服務不能接受新的請求。

單線程模型缺點:不適用於高負載,高併發的場景。

由於一個NIO線程若是同時處理不少的鏈路,則機器在性能上沒法知足海量的消息的編碼,解碼,讀取和發送。若是NIO線程負載太重,處理速度變慢,會致使大量的客戶端請求超時,甚至致使整個通訊模塊不可用。

Reactor多線程模型

爲了解決單線程模型的缺點,設計出了多線程模型。以下簡圖:

多線程模型簡圖

如圖所示在多線程模型下,用一個專門的NIO線程Acceptor來監聽客戶端的Tcp請求,對於網絡I/O的讀寫操做和消息的讀取、編碼、解碼、發送等使用NIO線程池來完成。由於客戶端請求數量大於NIO線程池中的線程,一個NIO線程能夠同時處理多條鏈路請求,可是一個鏈路請求只對應一個NIO線程。Reactor多線程模型可以大多數的使用場景,可是當客戶端的併發鏈接很是的多,或者是服務端須要對客戶端進行安全認證等,單個Acceptor線程可能會存在性能不足的問題。

主從Reactor多線程模型

Reactor的主從多線程模型

如圖所示,從這個簡圖能夠看出,服務端用於監聽和接收客戶端鏈接的再也不是單個線程,而是分配了一個線程池。Acceptor線程池接收了客戶端的請求鏈接並處理完成後(可能包含了權限認證等),後續的I/O操做再由NIO線程池來完成。這樣就解決了多線程中客戶端請求太多或者須要認證時一個Acceptor可能處理不過來的性能問題。

netty的線程模型

netty的線程模型是能夠經過設置啓動類的參數來配置的,設置不一樣的啓動參數,netty支持Reactor單線程模型、多線程模型和主從Reactor多線程模型

server端工做原理

img

NettyServer總體架構圖.png

server端啓動時綁定本地某個端口,將本身NioServerSocketChannel註冊到某個boss NioEventLoop的selector上。
server端包含1個boss NioEventLoopGroup和1個worker NioEventLoopGroup,NioEventLoopGroup至關於1個事件循環組,這個組裏包含多個事件循環NioEventLoop,每一個NioEventLoop包含1個selector和1個事件循環線程。
每一個boss NioEventLoop循環執行的任務包含3步:

  • 第1步:輪詢accept事件;
  • 第2步:處理io任務,即accept事件,與client創建鏈接,生成NioSocketChannel,並將NioSocketChannel註冊到某個worker NioEventLoop的selector上;
  • 第3步:處理任務隊列中的任務,runAllTasks。任務隊列中的任務包括用戶調用eventloop.execute或schedule執行的任務,或者其它線程提交到該eventloop的任務。

每一個worker NioEventLoop循環執行的任務包含3步:

  • 第1步:輪詢read、write事件;
  • 第2步:處理io任務,即read、write事件,在NioSocketChannel可讀、可寫事件發生時進行處理;
  • 第3步:處理任務隊列中的任務,runAllTasks。

client端工做原理

img

NettyClient總體架構圖.png

client端啓動時connect到server,創建NioSocketChannel,並註冊到某個NioEventLoop的selector上。
client端只包含1個NioEventLoopGroup,每一個NioEventLoop循環執行的任務包含3步:

  • 第1步:輪詢connect、read、write事件;
  • 第2步:處理io任務,即connect、read、write事件,在NioSocketChannel鏈接創建、可讀、可寫事件發生時進行處理;
  • 第3步:處理非io任務,runAllTasks。

服務端啓動時建立了兩個NioEventLoopGroup,一個是boss,一個是worker。實際上他們是兩個獨立的Reactor線程池,一個用於接收客戶端的TCP鏈接,另外一個用於處理Io相關的讀寫操做,或者執行系統/定時任務的task

簡單版

boss線程池做用:
(1)接收客戶端的鏈接,初始化Channel參數
(2)將鏈路狀態變動時間通知給ChannelPipeline

worker線程池做用:
(1)異步讀取通訊對端的數據報,發送讀事件到ChannelPipeline
(2)異步發送消息到通訊對端,調用ChannelPipeline的消息發送接口
(3)執行系統調用Task
(4)執行定時任務Task

經過配置boss和worker線程池的線程個數以及是否共享線程池等方式,netty的線程模型能夠在單線程、多線程、主從線程之間切換。

爲了提高性能,netty在不少地方都進行了無鎖設計。好比在IO線程內部進行串行操做,避免多線程競爭形成的性能問題。表面上彷佛串行化設計彷佛CPU利用率不高,可是經過調整NIO線程池的線程參數,能夠同時啓動多個串行化的線程並行運行,這種局部無鎖串行線程設計性能更優。

nettyd的NioEventLoop讀取到消息以後,直接調用ChannelPipeline的fireChannelRead(Object msg),只要用戶不主動切換線程,一直都是由NioEventLoop調用用戶的Handler,期間不進行線程切換,這種串行化設計避免了多線程操做致使的鎖競爭,性能角度看是最優的。

Netty 的三層架構設計

Netty 採用了典型的三層網絡架構進行設計和開發,其邏輯架構圖以下所示。

通訊調度層 Reactor

它由一系列輔助類完成,包括 Reactor 線程 NioEventLoop 及其父類,NioSocketChannel / NioServerSocketChannel 及其父類,Buffer 組件,Unsafe 組件 等。該層的主要職責就是監聽網絡的讀寫和鏈接操做,負責將網絡層的數據讀取到內存緩衝區,而後觸發各類網絡事件,例如鏈接建立、鏈接激活、讀事件、寫事件等,將這些事件觸發到 PipeLine 中,由 PipeLine 管理的責任鏈來進行後續的處理。

責任鏈層 Pipeline

它負責上述的各類網絡事件在責任鏈中的有序傳播,同時負責動態地編排責任鏈。責任鏈能夠選擇監聽和處理本身關心的事件,它能夠攔截處理事件,以及向前向後傳播事件。不一樣應用的 Handler 節點 的功能也不一樣,一般狀況下,每每會開發編解碼 Hanlder 用於消息的編解碼,能夠將外部的協議消息轉換成 內部的 POJO 對象,這樣上層業務則只須要關心處理業務邏輯便可,不須要感知底層的協議差別和線程模型差別,實現了架構層面的分層隔離。

業務邏輯編排層 Service ChannelHandler

業務邏輯編排層一般有兩類:一類是純粹的業務邏輯編排,還有一類是其餘的應用層協議插件,用於特定協議相關的會話和鏈路管理。例如,CMPP 協議,用於管理和中國移動短信系統的對接。

架構的不一樣層面,須要關心和處理的對象都不一樣,一般狀況下,對於業務開發者,只須要關心責任鏈的攔截和業務 Handler 的編排。由於應用層協議棧每每是開發一次,處處運行,因此實際上對於業務開發者來講,只須要關心服務層的業務邏輯開發便可。各類應用協議以插件的形式提供,只有協議開發人員須要關注協議插件,對於其餘業務開發人員來講,只需關心業務邏輯定製。這種分層的架構設計理念實現了 NIO 框架 各層之間的解耦,便於上層業務協議棧的開發和業務邏輯的定製。

正是因爲 Netty 的分層架構設計很是合理,基於 Netty 的各類應用服務器和協議棧開發纔可以如雨後春筍般獲得快速發展。

參考資料

漫話:如何給女友解釋什麼是Linux的五種IO模型?

《Netty權威指南》

聊聊同步、異步、阻塞與非阻塞 - 簡書

(22 封私信 / 21 條消息) 怎樣理解阻塞非阻塞與同步異步的區別? - 知乎

linux的五種IO模型 - lovejune - 博客園

Linux 下的五種 IO 模型詳細介紹_Linux_腳本之家

Linux IO模式及 select、poll、epoll詳解 - 人云思雲 - SegmentFault 思否

(2條消息)從bio到nio到netty實現原理淺析_嘎嘎的博客-CSDN博客_netty nio

深刻了解Netty【一】BIO、NIO、AIO簡單介紹 - clawhub - 博客園

Reactor 線程模型以及在netty中的應用 - balfish - 博客園

Netty框架之Reactor線程模型

一文看懂 Netty 架構設計

相關文章
相關標籤/搜索