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執行緒模型中的職責如下:
- 作為服務端Acceptor執行緒,負責處理客戶端的請求接入
- 作為客戶端Connecor執行緒,負責註冊監聽連線操作位,用於判斷非同步連線結果
- 作為IO執行緒,監聽網路讀操作位,負責從SocketChannel中讀取報文
- 作為IO執行緒,負責向SocketChannel寫入報文傳送給對方,如果發生寫半包,會自動註冊監聽寫事件,用於後續繼續傳送半包資料,直到資料全部發送完成
如下圖,是一個NioEventLoop的處理鏈:
- handler處理鏈中的處理方法是序列化執行的
- 一個客戶端連線只註冊到一個NioEventLoop上,避免了多個IO執行緒併發操作
OK,和netty相關的基礎知識就介紹完了,後面會陸續介紹netty的其他相關知識,歡迎交流學習...