首頁  >  文章  >  Java  >  Java中的netty原理是什麼

Java中的netty原理是什麼

爱喝马黛茶的安东尼
爱喝马黛茶的安东尼原創
2019-07-25 09:57:356861瀏覽

Java中的netty原理是什麼

一、 Netty簡介

#Netty是一個高效能、非同步事件驅動的NIO框架,基於JAVA NIO提供的API實作。它提供了對TCP、UDP和文件傳輸的支持,作為一個非同步NIO框架,Netty的所有IO操作都是非同步非阻塞的,透過Future-Listener機制,用戶可以方便的主動獲取或透過通知機制獲得IO操作結果。作為目前最受歡迎的NIO框架,Netty在網路領域、大數據分散式運算領域、遊戲產業、通訊產業等獲得了廣泛的應用,一些業界著名的開源元件也基於Netty的NIO框架建構。

二、Netty線程模型

在JAVA NIO方面Selector為Reactor模式提供了基礎,Netty結合Selector和Reactor模式設計了高效的線程模型。先看下Reactor模式:

2.1 Reactor模式

Wikipedia這麼解釋Reactor模型:「The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.」。首先Reactor模式首先是事件驅動的,有一個或多個並發輸入來源,有一個Server Handler和多個Request Handlers,這個Service Handler會同步的將輸入的請求多路復用的分發給對應的Request Handler。可以如下圖所示:

Java中的netty原理是什麼

從結構上有點類似生產者和消費者模型,即一個或多個生產者將事件放入一個Queue中,而一個或多個消費者主動的從這個佇列中poll事件來處理;而Reactor模式則沒有Queue來做緩衝,每當一個事件輸入到Service Handler之後,該Service Handler會主動根據不同的Evnent類型將其分發給對應的Request Handler來處理。

2.2 Reator模式的實作

關於Java NIO 建構Reator模式,Doug lea在《Scalable IO in Java》中給了很好的闡述,這裡截取PPT對Reator模式的實作進行說明

1.第一種實作模型如下: 

Java中的netty原理是什麼

這是最簡單的Reactor單執行緒模型,由於Reactor模式使用的是非同步非阻塞IO,所有的IO操作都不會被阻塞,理論上一個執行緒可以獨立處理所有的IO操作。這時Reactor線程是多面手,負責多路分離套接字,Accept新連接,並分發請求到處理鏈中。

對於一些小容量應用場景,可以使用到單執行緒模型。但對於高負載,大並發的應用卻不合適,主要原因如下:

(1)當一個NIO線程同時處理成百上千的鏈路,性能上無法支撐,即使NIO線程的CPU負載達到100%,也無法完全處理訊息

(2)當NIO執行緒負載過重後,處理速度會變慢,會導致大量客戶端連線逾時,逾時之後往往會重發,更重了NIO線程的負載。

(3)可靠性低,一個執行緒意外死循環,會導致整個通訊系統不可用。

為了解決這些問題,出現了Reactor多執行緒模型。

2.Reactor多執行緒模型: 

Java中的netty原理是什麼

#比較上一個模式,模型在處理鏈部分採用了多執行緒(執行緒池)。

在絕大多數場景下,此模型都能滿足效能需求。但是,在一些特殊的應用場景下,如伺服器會對客戶端的握手訊息進行安全認證。這類場景下,單獨的一個Acceptor執行緒可能會有效能不足的問題。為了解決這些問題,產生了第三種Reactor線程模型。

相關推薦:《java發展教學

3.Reactor主從模型 

Java中的netty原理是什麼

模型比較第二種模型,是將Reactor分成兩部分,mainReactor負責監聽server socket,accept新連線;並將建立的socket分派給subReactor。 subReactor負責多路分離已連接的socket,讀寫網路數據,對業務處理功能,其丟給worker線程池完成。通常,subReactor個數上可與CPU個數等同。

2.3 Netty模型

2.2中說完了Reactor的三種模型,那Netty是哪一種呢?其實Netty的線程模型是Reactor模型的變種,那就是去掉線程池的第三種形式的變種,這也是Netty NIO的預設模式。 Netty中Reactor模式的參與者主要有下面一些元件:

(1)Selector

#(2)EventLoopGroup/EventLoop

(3)ChannelPipeline

Selector即為NIO中提供的SelectableChannel多路復用器,充當著demultiplexer的角色,這裡不再贅述;下面對另外兩種功能和其在Netty之Reactor模式中扮演的角色進行介紹。

三、EventLoopGroup/EventLoop

當系統在運作過程中,如果頻繁的執行執行緒上下文切換,會帶來額外的效能損耗。多執行緒並發執行某個業務流程,業務開發者還需要時時對執行緒安全保持警惕,哪些資料可能會被並發修改,如何保護?這不僅降低了開發效率,也會帶來額外的效能損耗。

為了解決上述問題,Netty採用了串行化設計理念,從訊息的讀取、編碼以及後續Handler的執行,始終都由IO線程EventLoop負責,這就意外著整個流程不會進行線程上下文的切換,資料也不會面臨被並發修改的風險。這也解釋了為什麼Netty線程模型去掉了Reactor主從模型中線程池。

EventLoopGroup是一組EventLoop的抽象,EventLoopGroup提供next接口,可以總一組EventLoop裡面按照一定規則獲取其中一個EventLoop來處理任務,對於EventLoopGroup這裡需要了解的是在Netty中,在Netty伺服器程式設計中我們需要BossEventLoopGroup和WorkerEventLoopGroup兩個EventLoopGroup來進行工作。通常一個服務端口即一個ServerSocketChannel對應一個Selector和一個EventLoop線程,也就是說BossEventLoopGroup的線程數參數為1。 BossEventLoop負責接收客戶端的連線並將SocketChannel交給WorkerEventLoopGroup來進行IO處理。

EventLoop的實作扮演Reactor模式中的分發(Dispatcher)的角色。

四、ChannelPipeline

ChannelPipeline其實是擔任著Reactor模式中的請求處理器這個角色。

ChannelPipeline的預設實作是DefaultChannelPipeline,DefaultChannelPipeline本身維護著一個使用者不可見的tail和head的ChannelHandler,他們分別位於鍊錶隊列的頭部和尾部。 tail在較上層的部分,而head在靠近網路層的方向。在Netty中關於ChannelHandler有兩個重要的介面,ChannelInBoundHandler和ChannelOutBoundHandler。 inbound可以理解為網路資料從外部流向系統內部,而outbound可以理解為網路資料從系統內部流向系統外部。用戶實現的ChannelHandler可以根據需要實現其中一個或多個接口,將其放入Pipeline中的鍊錶隊列中,ChannelPipeline會根據不同的IO事件類型來找到相應的Handler來處理,同時鍊錶隊列是責任鏈模式的一種變種,自上而下或自下而上所有滿足事件關聯的Handler都會對事件進行處理。

ChannelInBoundHandler對從客戶端發送到伺服器的封包進行處理,一般用來執行半包/黏包,解碼,讀取數據,業務處理等;ChannelOutBoundHandler對從伺服器發送到客戶端的封包進行處理,一般用來進行編碼,發送報文到客戶端。

下圖是對ChannelPipeline執行過程的說明: 

Java中的netty原理是什麼

#關於Pipeline的更多知識可參考:淺談管道模型(Pipeline)

五、Buffer

Netty提供的經過擴展的Buffer相對NIO中的有個許多優勢,作為資料存取非常重要的一塊,我們來看看Netty中的Buffer有什麼特色。

1.ByteBuf讀寫指標

在ByteBuffer中,讀寫指標都是position,而在ByteBuf中,讀寫指標分別為readerIndex和writerIndex,直觀看上去ByteBuffer僅用了一個指標實現了兩個指標的功能,節省了變量,但是當對於ByteBuffer的讀寫狀態切換的時候必須要調用flip方法,而當下一次寫之前,必須要將Buffe中的內容讀完,再調用clear方法。每次讀之前調用flip,寫之前調用clear,這樣無疑為開發帶來了繁瑣的步驟,內容沒有讀完是不能寫的,這樣非常不靈活。相較之下我們看看ByteBuf,讀的時候只依賴readerIndex指針,寫的時候僅依賴writerIndex指針,不需每次讀寫之前調用對應的方法,而且沒有必須一次讀完的限制。

2.零拷貝

(1)Netty的接收與發送ByteBuffer採用DIRECT BUFFERS,使用堆外直接記憶體進行Socket讀寫,不需要進行位元組緩衝區的二次拷貝。如果使用傳統的堆疊記憶體(HEAP BUFFERS)進行Socket讀寫,JVM會將堆疊記憶體Buffer拷貝一份到直接記憶體中,然後才寫入Socket。相較於堆外直接內存,訊息在傳送過程中多了一次緩衝區的記憶體拷貝。

(2)Netty提供了組合Buffer對象,可以聚合多個ByteBuffer對象,用戶可以像操作一個Buffer那樣方便的對組合Buffer進行操作,避免了傳統通過內存拷貝的方式將幾個小Buffer合併成一個大的Buffer。

(3)Netty的檔案傳輸採用了transferTo方法,它可以直接將檔案緩衝區的資料傳送到目標Channel,避免了傳統透過循環write方式導致的記憶體拷貝問題。

3.引用計數與池化技術

在Netty中,每個被申請的Buffer對於Netty來說都可能是很寶貴的資源,因此為了獲得對於記憶體的申請與回收更多的控制權,Netty自己根據引用計數法去實現了記憶體的管理。 Netty對於Buffer的使用都是基於直接記憶體(DirectBuffer)實現的,大大提高I/O操作的效率,然而DirectBuffer和HeapBuffer相比之下除了I/O操作效率高之外還有一個天生的缺點,即DirectBuffer的申請相比HeapBuffer效率更低,因此Netty結合引用計數實現了PolledBuffer,即池化的用法,當引用計數等於0的時候,Netty將Buffer回收致池中,在下一次申請Buffer的沒某個時刻會被復用。

總結

Netty其實本質上就是Reactor模式的實現,Selector作為多工器,EventLoop作為轉發器,Pipeline作為事件處理器。但是和一般的Reactor不同的是,Netty使用串行化實現,並在Pipeline中使用了責任鏈模式。

Netty中的buffer相對有NIO中的buffer又做了一些優化,大大提高了效能。

以上是Java中的netty原理是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn