Netty的線程模型
1.1主從的 Reactor多線程模型
服務端用于接受用戶端的連接配接不再是一個單獨的NIO線程,而是一個獨立的NIO線程池,Accertor接收到用戶端的TCP連接配接請求之後,将新建立的SocketChannel注冊到 I/O 線程池 netty使用的是這種。
服務端啟動的時候,建立了兩個NioEventLoop,它們是兩個獨立的Reactor線程池,一個用于接受TCP的連接配接,另一個用于處理I/O相關的讀寫操作,或者執行系統的Task,定時任務Task。
用于接受用戶端連接配接的線程池職責是:
- 接受用戶端的連接配接,初始化Channel參數
- 将鍊路狀态變更事件 通知給ChannelPipline
用于處理I/O操作的線程池職責如下:
- 異步讀取通信對端的資料包,發送讀事件到ChannelPipline;
- 異步發送消息到通信對端,調用ChannelPipline的消息發送接口;
- 執行系統的Task;
- 執行定時的Task,例如鍊路空閑檢測定時任務;
1.2 基于該線程模型的最佳實踐
- 建立兩個NioEventLoopGroup 用于隔離Nio Acceptor和Nio I/O 線程;
- 盡量不要再ChannelHandler 中啟動使用者線程(解碼後用于将POJO 消息派發到後端服務端除外);
- 解碼要放在Nio的解碼Handler 中進行,不要切換到使用者線程中解碼;
- 如果業務邏輯非常簡單,沒有導緻線程阻塞的磁盤操作,資料庫操作,網絡操作,可以在NIO線程完成業務處理;
- 如果業務邏輯比較複雜,不要再NIO線程完成,建議将解碼後的pojo消息封裝為task,派發到業務線程池中有業務線程處理,保證Nio線程盡快被釋放,處理其他的I/O操作。
1.2.1 推薦的線程數量計算公式
- 線程數量 = (線程總時間 / 瓶頸資源時間)* 瓶頸資源的線程并行數
NioEventLoop的源碼分析
2.1 NioEventLoop的設計原理
NioEventLoop 并不是一個純粹的I/O線程 ,除了負責I/O操作,還兼顧處理一下任務:
- 系統Task:通過調用execute(runable)方法實作,Netty有很多的系統task,建立他們的主要原因是:當I/O操作線程和使用者線程同時操作網絡資源時,為了防止并發導緻鎖競争,将使用者的線程的操作封裝為task 放入消息對列中,讓I/O線程去處理,這樣實作了局部無鎖。
- 定時任務:調用schedule(runable,delay,timeunit)來實作。
繼承類圖
NioEventLoop
2.2run方法
2.3 出現bug的原因
服務端等待連接配接,用戶端發起連接配接,發送消息,服務端接受連接配接,并注冊監聽通道的OP_READ,服務端讀取消息,從感興趣事件集合中移除OP_READ,用戶端關閉連接配接,服務端給用戶端發送消息,服務端select方法不再阻塞,無限喚醒并且傳回值為0.
2.4 輪循到就緒狀态SocketChannel 處理I/O
processSelectedKey 方法分析