見字如面,我是威哥,一個從普通二本院校畢業,從未曾接觸分布式、微服務、高并發到通過技術分享實作職場蛻變,成長為RocketMQ社區優秀布道師、大廠資深架構師,出版《RocketMQ技術內幕》一書,在CSDN中記錄了我的成長歷程,歡迎大家關注我,隨時可私信我,一起交流進步,
該系列已分別介紹了服務端、客戶端的啟動流程,本文將重點剖析Netty是如何封裝NIO的讀事件,
溫馨提示:本文雖然是原始碼分析,但強烈建議精讀,因為根據原始碼闡述其背后的設計哲學,也用黑體進行了標注,請特別留意,
在閱讀本篇文章之前,請稍微思考如下幾個問題:
- NIO為什么不適合檔案上傳等場景
- NIO如何避免一個超大資料傳送的連接對其他請求的影響
- NIO如何處理半關閉
文章目錄
- 1、讀事件概述
- 2、IO讀事件從處理流程
- 3、接受連接處理流程
1、讀事件概述
關于Read事件在SocketChannel與ServerSocketChannel所對應的操作不一樣,在SocketChannel中,則對應資料讀,而在ServerSocketChannel中則被被封裝成接受客戶端的連接請求,
NIO read事件入口在NioEventLoop的processSelectedKey方法,截圖如下:

其核心入口為UnSafe的read方法,關于UnSafe的類層次結構如下圖所示:

- AbstractNioByteChannel$NioByteUnsafe#read
SocketChannel對應的讀事件處理流程,即IO讀的處理實作, - AbstractNioMessageChannel$NioMessageUnsafe#read
ServerSocketChannle對應的讀事件處理流程,
接下來將分別介紹這兩個流程,
2、IO讀事件從處理流程
IO讀事件由AbstractNioByteChannel內部類AbstractNioUnsafe的read方法實作,接下來重點剖析該方法,從中窺探Netty對IO讀事件的處理,

Step1:如果沒有開啟自動注冊讀事件,在每一次讀時間處理過后會取消讀事件,默認為自動注冊,
溫馨提示:如果通道不注冊讀事件,將無法從通道中讀取資料,即無法處理請求或接受回應,
如果沒有開啟自動讀事件,需要應用程式在需要的時候手動呼叫通道的read方法,
取消讀事件,Netty基于NIO給出了非常標準的實作,基本可以當場模板代碼使用:

其實作關鍵點:首先判斷鍵值對是否有效,然后通過位運算進行取消注冊,

Step2:創建接受快取區記憶體分配器,這里有兩個關鍵點:
- maxMessagesPerRead
每一個通道在一次讀事件處理程序中最多可以呼叫底層Socket進行讀取的次數,默認為16次,這里的設計哲學是避免一個通道需要讀取太多的資料,從而影響其他通道的資料讀,因為在一個事件選擇器中多個通道的讀事件是串行執行的, - RecvByteBufAllocator
接受緩沖區的記憶體分配策略,分為分配固定大小(不夠時擴容)、動態變化(根據歷史分配的大小,動態條件合適的記憶體大小),這里主要的設計哲學是合理利用記憶體,并減少擴容,提高記憶體的分配效率與使用效率,

Step3:回圈處理讀事件,最多處理maxMessagePerRead,接下來探討一下單次讀事件的處理流程,

Step4:進行一次IO讀處理,其處理有如下幾個關鍵點:
- 首先分配一個ByteBuf,俗稱接收快取區,用來存放從網路中讀取的內容,
- 獲取一下分配到的累積快取區可寫的位元組數,這個后面有妙用,
- 呼叫底層網路讀API從網卡中讀取資料,NIO的讀取實作如下所示:

即呼叫NIO中的SocketChannel進行讀資料,其回傳引數表示這次從網卡中讀取到的位元組數,如果讀取到的位元組少于0,則表示對端通道已關閉,己端也需要進行相應的處理,例如關閉通道, - 讀到一批資料后,會通過事件傳播機制向事件鏈中傳播channelRead事件,觸發后續對該批資料的處理,

Step5:判斷該通道是否需要繼續讀,其基本依據如下:
- 如果未開啟自動注冊讀事件,讀完一次之后將不再繼續讀取,
- 如果本次讀取到的位元組數小于接收快取區,說明此刻網卡中沒有可讀資料,等下一次讀事件觸發再繼續讀,

Step6:一次或多次讀操作結束后,會觸發一次讀完成事件,向整個事件鏈傳播,
整個網路讀處理流程就介紹到這了,
3、接受連接處理流程
在Netty中,服務端接收客戶端的連接請求(OP_ACCEPT),被封裝在channelRead 事件中,其代碼入口為:AbstractNioMessageChannel 的內部類NioMessageUnsafe的read方法,

其大概的實作要點在前面已經介紹,這里主要看一下NioServerSocketChannel的doReadMessage,

通過使用底層的NIO接受一個連接,并獲取NioSocketChannel物件,然后繼續該物件向下傳播channelRead事件,在后續的處理器中對該物件進行操作,例如將其注冊讀事件,從而觸發網路的讀操作,關于NioSocketChannel如何系結讀事件、注冊業務相關的事件監聽器機制已經在Netty進階:手把手教你如何撰寫一個NIO服務端中詳細介紹,本文就不再重復,
好了,本文就介紹到這里了,想必對開頭部門提出的問題有了自己的答案了吧,您的一鍵三連是對我最大的鼓勵,當然可以加筆者微信:dingwpmz,備注CSDN,共同交流探討,
為了方便大家學習Netty,筆者將RocketMQ的網路通信模塊抽取出一個通用的Netty開發框架,大家可以從github上下載,堪稱Netty界最強Hello World,

下載鏈接:Netty通用開發框架github地址
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/276745.html
標籤:其他
上一篇:Redis使用去中心化分片集群
下一篇:位元組跳動面試官問我什么是Zookeeper、Zookeeper的應用場景、服務呼叫、資料模型與節點分類,我整理了下!
