1. 程式人生 > >Netty之框架原理分析(一)

Netty之框架原理分析(一)

https://blog.csdn.net/qq_18603599/article/details/80768390

netty是典型基於reatctor模型的程式設計,主要用於完成網路底層通訊的,java本身也是提供各種io的操作,但是使用起來api會很繁瑣,同時效能有很難有保證,經常會出現莫名其妙的bug,所以為了方便開發者更好的把精力集中於業務,讓netty來封裝一切繁瑣的工作,對開發者透明化,大大降低了開發門檻,所以從本章開始就完全的介紹一下netty的相關知識,今天主要介紹的內容知識點如下:

1 IO模型分類
2 程式設計模型分類
3 Netty的執行緒模型分類
4 Netty的NIO執行緒實現機制

同步阻塞:呼叫會一直阻塞到資料接收完畢

同步非阻塞:呼叫會一直迴圈,直到接收到資料為止

IO複用

& select/poll

流程:

》註冊待偵聽的fd(這裡的fd建立時最好使用非阻塞)

》每次呼叫都去檢查這些fd的狀態,當有一個或者多個fd就緒的時候返回

》返回結果中包括已就緒和未就緒的fd

優點:解決了單個程序能夠開啟的檔案描述符數量有限制這個問題

缺點:

》每次呼叫,都需要把fd集合從使用者態拷貝到核心態,這個開銷在fd很多的時候會很大

》需要在核心遍歷所有傳進來的fd,這個fd很多的時候,開銷也很大

》支援的檔案描述符數量有限,預設為1024

& epoll

流程:

》呼叫epoll_create建立一個epoll物件

》呼叫epoll_ctl向epoll物件中新增連線的套接字

》呼叫epoll_wait收集發生的事件的連線

優點:

基於事件驅動的方式,避免了每次都要把所有fd都掃描一遍

epoll_wait只返回就緒的fd

epoll使用nmap記憶體對映技術避免了記憶體複製的開銷

poll的fd數量上限是作業系統的最大檔案控制代碼數目,這個數目一般和記憶體有關,通常遠大於1024

訊號驅動:

》開啟套接字訊號驅動IO功能

》系統呼叫sigaction執行訊號處理函式(非阻塞,立刻返回)

》資料就緒,生成sigio訊號,通過訊號回撥通知應用來讀取資料。

 

非同步非阻塞/事件驅動IO:告知核心啟動某個操作,等操作完成之後來主動通知我們

接下來再整理一下,常用的程式設計模型

BIO:同步阻塞

流程:

》主執行緒accept請求阻塞

》請求到達,建立新的執行緒來處理這個套接字,完成對客戶端的響應

》主執行緒繼續accept下一個請求

缺點:

》當客戶端連線增多時,服務端建立的執行緒也會暴漲,系統性能會急劇下降

》服務端的執行緒個數和客戶端併發訪問的個數是1:1

NIO:非同步非阻塞

流程:

》建立ServerSocketChannel監聽客戶端連線並繫結監聽埠,設定為非阻塞模式

》建立Reactor執行緒,建立多路複用器(Selector)並啟動執行緒

》將ServerSocketChannel註冊到Reactor執行緒的Selector上。監聽accept事件

》selector執行緒在run方法中無限迴圈輪詢準備就緒的key

》Selector監聽到新的客戶端接入,處理新的請求,完成tcp三次握手,建立物理連線

》將新的客戶端連線註冊到Selector上,監聽讀操作。讀取客戶端傳送的網路訊息

》客戶端傳送的資料就緒則讀取客戶端請求,進行處理。

優點:實現了io的多路複用功能

缺點:程式設計實現起來非常複雜

AIO/NIO.2:當進行讀寫操作時,只須直接呼叫API的read或write方法即可

》對於讀操作而言,當有流可讀取時,作業系統會將可讀的流傳入read方法的緩衝區,並通知應用程式

》對於寫操作而言,當作業系統將write方法傳遞的流寫入完畢時,作業系統主動通知應用程式

同步/非同步:是對於服務端讀取資料而言

阻塞/非阻塞:是對於客戶端請求的

 

接下來看原始java的nio實現,其實最主要就是三個類:

 

核心類

Channel:資料的傳輸管道

Buffer:資料緩衝區

Selector:根據key處理對應的channel

和核心流程:

 

1 開啟serversocketchannel

2 繫結監聽地址ip

 

3 建立selector啟動執行緒

4 把serversocketchannel註冊到selector 監聽

5 selector輪詢就緒的key

 

6 handleAccept處理新的客戶端接入

7 設定新建客戶端連線的socket引數

8 向selector註冊監聽讀操作

9 handleRead非同步讀請求訊息到bytebuffer

10 decode請求訊息

11 非同步寫bytebuffer到socketchannel

 

看到上面的步驟還說很繁瑣的,如果用原始的api開發,這些都是需要開發者自己考慮到並且要能夠完善,但是我們用netty的話,上面這些就不是我們太關注的了,簡化了很多,所以接下來就看看netty的執行緒模型機制:

總共有三種

一、Reactor模型單執行緒模型如下: 
這裡寫圖片描述

  • 使用者發起IO操作到事件分離器
  • 事件分離器呼叫相應的處理器處理事件
  • 事件處理完成,事件分離器獲得控制權,繼續相應處理

缺點:

》 效能有極限,不能處理成百上千的事件 
》 當負荷達到一定程度時,效能將會下降 
》某一個事件處理器傳送故障,不能繼續處理其他事件

 

二、Reactor模型多執行緒模型如下: 

這裡寫圖片描述

機制:

 

》有專門一個NIO執行緒-Acceptor執行緒用於監聽服務端,接收client的TCP連線請求

》網路IO操作-讀、寫等由一個NIO執行緒池負責

》1個NIO執行緒能夠同一時候處理N條鏈路。可是1個鏈路僅僅相應1個NIO執行緒,防止發生併發操作問題。

缺點:監聽服務的只是一個單執行緒,還說會存在效能瓶頸
 

三、Reactor主從多執行緒模型

  • Acceptor(boss執行緒池)不再是一個單獨的NIO執行緒,而是一個獨立的NIO執行緒池
  • Acceptor(boss執行緒池)處理完後,將事件註冊到IO執行緒池(work執行緒池)的某個執行緒上
  • IO執行緒繼續完成後續的IO操作
  • Acceptor(boss執行緒池)僅僅完成登入、握手和安全認證等操作,IO(work執行緒池)操作和業務處理依然在後面的從執行緒中完成

這裡寫圖片描述

優點:監聽和處理都是執行緒池,且是獨立的nio執行緒池

那麼在netty中是通過NioEventLoop實現的,

 

NioEventLoop是Netty的Reactor執行緒,它在Netty Reactor執行緒模型中的職責如下:

  1. 作為服務端Acceptor執行緒,負責處理客戶端的請求接入
  2. 作為客戶端Connecor執行緒,負責註冊監聽連線操作位,用於判斷非同步連線結果
  3. 作為IO執行緒,監聽網路讀操作位,負責從SocketChannel中讀取報文
  4. 作為IO執行緒,負責向SocketChannel寫入報文傳送給對方,如果發生寫半包,會自動註冊監聽寫事件,用於後續繼續傳送半包資料,直到資料全部發送完成 
    如下圖,是一個NioEventLoop的處理鏈:

這裡寫圖片描述

  • handler處理鏈中的處理方法是序列化執行的
  • 一個客戶端連線只註冊到一個NioEventLoop上,避免了多個IO執行緒併發操作

OK,和netty相關的基礎知識就介紹完了,後面會陸續介紹netty的其他相關知識,歡迎交流學習...