Netty 原理

? ? ? ? 前段時(shí)間在看RocketMQ源碼時(shí),發(fā)現(xiàn)底層用了Netty,順便學(xué)習(xí)了一下,網(wǎng)上不少博客講的有錯誤之處,而且大部分一模一樣,估計(jì)大部分都是復(fù)制別人的。為了不被誤導(dǎo),我專門買了本《Netty權(quán)威指南》,仔細(xì)閱讀了一遍,而且微信請教了鋒哥(李林鋒),遂整理出這篇分享。

本人一直秉承原則:寧愿不寫、少寫,也盡量不寫錯的知識!以免誤人子弟!

希望轉(zhuǎn)載的同學(xué),標(biāo)出原文鏈接。謝謝!同時(shí)非常歡迎指出錯誤,本人及時(shí)修正!


版本說明

Netty3(3.x)版本是比較舊的版本。

Netty4(4.x)版本是當(dāng)前官方推薦的,目前一直在維護(hù)中。跟3.x版本相比變化比較大,特別是API。

Netty5(5.x)是被舍棄的版本,官方不推薦使用!

Netty5舍棄的官方解釋:

1. netty5 中使用了 ForkJoinPool,增加了代碼的復(fù)雜度,但是對性能的改善卻不明顯

2. 多個(gè)分支的代碼同步工作量很大

3. 作者覺得當(dāng)下還不到發(fā)布一個(gè)新版本的時(shí)候

4. 在發(fā)布版本之前,還有更多問題需要調(diào)查一下,比如是否應(yīng)該廢棄 exceptionCaught,是否暴露EventExecutorChooser等等。

參考:https://github.com/netty/netty/issues/4466


1. Netty基本概念

Netty是一個(gè)高性能、異步事件驅(qū)動的NIO框架,它提供了對TCP、UDP和文件傳輸?shù)闹С郑鳛橐粋€(gè)異步NIO框架,Netty的所有IO操作都是異步非阻塞的,通過Future-Listener機(jī)制,用戶可以方便的主動獲取或者通過通知機(jī)制獲得IO操作結(jié)果。

作為當(dāng)前最流行的NIO框架,Netty在互聯(lián)網(wǎng)領(lǐng)域、大數(shù)據(jù)分布式計(jì)算領(lǐng)域、游戲行業(yè)、通信行業(yè)等獲得了廣泛的應(yīng)用,一些業(yè)界著名的開源組件也基于Netty的NIO框架構(gòu)建。如:Dubbo、 RocketMQ、Hadoop的Avro、Spark等。

Netty需要學(xué)習(xí)的內(nèi)容: 編解碼器、TCP粘包/拆包及Netty如何解決、ByteBuf、Channel和Unsafe、ChannelPipeline和ChannelHandler、EventLoop和EventLoopGroup、Future等。

編解碼器:

Java序列化的目的主要有兩個(gè):

網(wǎng)絡(luò)傳輸

對象持久化

Java序列化僅僅是Java編解碼技術(shù)的一種,由于他的種種缺陷,衍生出多種編解碼技術(shù)和框架。

Java序列化的缺點(diǎn):

無法跨語言

序列化后的碼流太大

序列化性能太低

業(yè)界主流的編解碼框架:

Google Protobuf:支持Java、C++、Python三種語言,高效的編碼性能,結(jié)構(gòu)化數(shù)據(jù)存儲格式(XML,JSON等)

Facebook Thrift:適用于靜態(tài)的數(shù)據(jù)交換,需要先確定好它的數(shù)據(jù)結(jié)構(gòu)。結(jié)構(gòu)變化后需要重新編譯IDL文件,這也是Thrift的弱項(xiàng)。

JBoss Marshalling:是一個(gè)Java對象的序列化API,修正了JDK自帶的序列化包的很多問題,但又保持跟java.io.Serializable接口的兼容。

Hessian:一個(gè)輕量級的remoting onhttp工具,使用簡單的方法提供了RMI的功能,采用的是二進(jìn)制RPC協(xié)議。

TCP粘包/拆包:

TCP是個(gè)“流”協(xié)議,所謂流,就是沒有界限的一串?dāng)?shù)據(jù)。TCP底層并不了解上層業(yè)務(wù)數(shù)據(jù)的具體含義,它會根據(jù)TCP緩存區(qū)的實(shí)際情況進(jìn)行包的劃分,所以在業(yè)務(wù)上的一個(gè)完整的包,可能被TCP拆分成多個(gè)包進(jìn)行發(fā)送,也可能把多個(gè)小包封裝成一個(gè)大的數(shù)據(jù)包發(fā)送,這就是TCP粘包和拆包問題。

有如下幾種情況:

正常情況
粘包
粘包和拆包同時(shí)發(fā)生


2. Netty線程模型

在JAVA NIO方面Selector給Reactor模式提供了基礎(chǔ),Netty結(jié)合Selector和Reactor模式設(shè)計(jì)了高效的線程模型。

關(guān)于Java NIO 構(gòu)造Reator模式,Doug Lea在《Scalable IO in Java》中給了很好的闡述,這里截取PPT對Reator模式的實(shí)現(xiàn)進(jìn)行說明。

(1)Reactor單線程模型

單線程模型

這是最簡單的Reactor單線程模型,由于Reactor模式使用的是異步非阻塞IO,所有的IO操作都不會被阻塞,理論上一個(gè)線程可以獨(dú)立處理所有的IO操作。這時(shí)Reactor線程是個(gè)多面手,負(fù)責(zé)多路分離套接字,Accept新連接,并分發(fā)請求到處理鏈中。

對于一些小容量應(yīng)用場景,可以使用到單線程模型。但對于高負(fù)載,大并發(fā)的應(yīng)用卻不合適,主要原因如下:

1. 當(dāng)一個(gè)NIO線程同時(shí)處理成百上千的鏈路,性能上無法支撐,即使NIO線程的CPU負(fù)荷達(dá)到100%,也無法完全處理消息。

2. 當(dāng)NIO線程負(fù)載過重后,處理速度會變慢,會導(dǎo)致大量客戶端連接超時(shí),超時(shí)之后往往會重發(fā),更加重了NIO線程的負(fù)載。

3. 可靠性低,一個(gè)線程意外死循環(huán),會導(dǎo)致整個(gè)通信系統(tǒng)不可用。

為了解決這些問題,出現(xiàn)了Reactor多線程模型。

(2)Reactor多線程模型

多線程模型

相比上一種模式,該模型在處理鏈部分采用了多線程(線程池)。

在絕大多數(shù)場景下,該模型都能滿足性能需求。但是,在一些特殊的應(yīng)用場景下,如服務(wù)器會對客戶端的握手消息進(jìn)行安全認(rèn)證。這類場景下,單獨(dú)的一個(gè)Acceptor線程可能會存在性能不足的問題。為了解決這些問題,產(chǎn)生了第三種Reactor線程模型。

(3)Reactor主從模型?

主從模型

該模型相比第二種模型,是將Reactor分成兩部分,mainReactor負(fù)責(zé)監(jiān)聽server socket,accept新連接;并將建立的socket分派給subReactor。subReactor負(fù)責(zé)多路分離已連接的socket,讀寫網(wǎng)絡(luò)數(shù)據(jù),對業(yè)務(wù)處理功能,其扔給worker線程池完成。通常,subReactor個(gè)數(shù)上可與CPU個(gè)數(shù)等同。

利用主從NIO線程模型,可以解決一個(gè)服務(wù)端監(jiān)聽線程無法有效處理所有客戶端連接的性能不足問題,因此,在Netty的官方Demo中,推薦使用該線程模型。

(4)Netty模型

Netty的線程模型并不是一成不變,它實(shí)際取決于用戶的啟動參數(shù)配置。通過設(shè)置不同的啟動參數(shù),Netty可以同時(shí)支持Reactor單線程模型、多線程模型和主從模型。

前面介紹完 Netty 相關(guān)一些理論,下面從功能特性、模塊組件來介紹 Netty 的架構(gòu)設(shè)計(jì)。


3. Netty功能特性

Netty的功能特性

4. Netty模塊組件

Netty主要有下面一些組件:

Selector

NioEventLoop

NioEventLoopGroup

ChannelHandler

ChannelHandlerContext

ChannelPipeline


Selector

Netty 基于 Selector 對象實(shí)現(xiàn) I/O 多路復(fù)用,通過 Selector 一個(gè)線程可以監(jiān)聽多個(gè)連接的 Channel 事件。

NioEventLoop

其中維護(hù)了一個(gè)線程和任務(wù)隊(duì)列,支持異步提交執(zhí)行任務(wù),線程啟動時(shí)會調(diào)用 NioEventLoop 的 run 方法,執(zhí)行 I/O 任務(wù)和非 I/O 任務(wù)。

NioEventLoopGroup

主要管理 eventLoop 的生命周期,可以理解為一個(gè)線程池,內(nèi)部維護(hù)了一組線程,每個(gè)線程(NioEventLoop)負(fù)責(zé)處理多個(gè) Channel 上的事件,而一個(gè) Channel 只對應(yīng)于一個(gè)線程。

ChannelHandler

是一個(gè)接口,處理 I/O 事件或攔截 I/O 操作,并將其轉(zhuǎn)發(fā)到其 ChannelPipeline(業(yè)務(wù)處理鏈)中的下一個(gè)處理程序。

ChannelHandlerContext

保存 Channel 相關(guān)的所有上下文信息,同時(shí)關(guān)聯(lián)一個(gè) ChannelHandler 對象。

ChannelPipeline 是保存 ChannelHandler 的 List,用于處理或攔截 Channel 的入站事件和出站操作。實(shí)現(xiàn)了一種高級形式的攔截過濾器模式,使用戶可以完全控制事件的處理方式,以及 Channel 中各個(gè)的 ChannelHandler 如何相互交互。

ChannelPipeline對事件流的攔截和處理流程:

ChannelPipeline入站出站圖

Netty中的事件分為Inbond事件和Outbound事件。

Inbound事件通常由I/O線程觸發(fā),如TCP鏈路建立事件、鏈路關(guān)閉事件、讀事件、異常通知事件等。

Outbound事件通常是用戶主動發(fā)起的網(wǎng)絡(luò)I/O操作,如用戶發(fā)起的連接操作、綁定操作、消息發(fā)送等。


在 Netty中,Channel 、ChannelHandler、ChannelHandlerContext、 ChannelPipeline的關(guān)系如下圖:

各組件關(guān)系圖

一個(gè) Channel 包含了一個(gè) ChannelPipeline,而 ChannelPipeline 中又維護(hù)了一個(gè)由 ChannelHandlerContext 組成的雙向鏈表,并且每個(gè) ChannelHandlerContext 中又關(guān)聯(lián)著一個(gè) ChannelHandler。

入站事件和出站事件在一個(gè)雙向鏈表中,入站事件會從鏈表 head 往后傳遞到最后一個(gè)入站的 handler,出站事件會從鏈表 tail 往前傳遞到最前一個(gè)出站的 handler,兩種類型的 handler 互不干擾。


以上是Netty的主要原理介紹,Netty源碼分析的話,后續(xù)有時(shí)間會繼續(xù)分享出來。

----------------------------------------------------------

作者:輪回的背影

原文:Netty 基本原理 - 輪回的博客 - CSDN博客

版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容