1. 程式人生 > >[轉]詳解netty原理分析

[轉]詳解netty原理分析

詳解netty原理分析

 

Netty是一個高效能、非同步事件驅動的NIO框架,它提供了對TCP、UDP和檔案傳輸的支援,作為一個非同步NIO框架,Netty的所有IO操作都是非同步非阻塞的,通過Future-Listener機制,使用者可以方便的主動獲取或者通過通知機制獲得IO操作結果。

作為當前最流行的NIO框架,Netty在網際網路領域、大資料分散式計算領域、遊戲行業、通訊行業等獲得了廣泛的應用,一些業界著名的開源元件也基於Netty的NIO框架構建。

Netty架構分析

Netty 採用了比較典型的三層網路架構進行設計,邏輯架構圖如下所示:

第一層:Reactor 通訊排程層,它由一系列輔助類完成,包括 Reactor 執行緒 NioEventLoop 以及其父類、NioSocketChannel/NioServerSocketChannel 以及其父類、ByteBuffer 以及由其衍生出來的各種 Buffer、Unsafe 以及其衍生出的各種內部類等。該層的主要職責就是監聽網路的讀寫和連線操作,負責將網路層的資料讀取到記憶體緩衝區中,然後觸發各種網路事件,例如連線建立、連線啟用、讀事件、寫事件等等,將這些事件觸發到 PipeLine 中,由 PipeLine 充當的職責鏈來進行後續的處理。

第二層:職責鏈 PipeLine,它負責事件在職責鏈中的有序傳播,同時負責動態的編排職責鏈,職責鏈可以選擇監聽和處理自己關心的事件,它可以攔截處理和向後/向前傳播事件,不同的應用的 Handler 節點的功能也不同,通常情況下,往往會開發編解碼 Hanlder 用於訊息的編解碼,它可以將外部的協議訊息轉換成內部的 POJO 物件,這樣上層業務側只需要關心處理業務邏輯即可,不需要感知底層的協議差異和執行緒模型差異,實現了架構層面的分層隔離。

第三層:業務邏輯處理層,可以分為兩類:

1.純粹的業務邏輯處理,例如訂單處理。

2.應用層協議管理,例如HTTP協議、FTP協議等。

接下來,我從影響通訊效能的三個方面(I/O模型、執行緒排程模型、序列化方式)來談談Netty的架構。

IO模型

Netty的I/O模型基於非阻塞I/O實現,底層依賴的是JDK NIO框架的Selector。

Selector提供選擇已經就緒的任務的能力。簡單來講,Selector會不斷地輪詢註冊在其上的Channel,如果某個Channel上面有新的TCP連線接入、讀和寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以獲取就緒Channel的集合,進行後續的I/O操作。

執行緒排程模型

常用的Reactor執行緒模型有三種,分別如下:

1.Reactor單執行緒模型:Reactor單執行緒模型,指的是所有的I/O操作都在同一個NIO執行緒上面完成。對於一些小容量應用場景,可以使用單執行緒模型。

2.Reactor多執行緒模型:Rector多執行緒模型與單執行緒模型最大的區別就是有一組NIO執行緒處理I/O操作。主要用於高併發、大業務量場景。

3.主從Reactor多執行緒模型:主從Reactor執行緒模型的特點是服務端用於接收客戶端連線的不再是個1個單獨的NIO執行緒,而是一個獨立的NIO執行緒池。利用主從NIO執行緒模型,可以解決1個服務端監聽執行緒無法有效處理所有客戶端連線的效能不足問題。

序列化方式

影響序列化效能的關鍵因素總結如下:

1.序列化後的碼流大小(網路頻寬佔用)

2.序列化&反序列化的效能(CPU資源佔用)

3.併發呼叫的效能表現:穩定性、線性增長、偶現的時延毛刺等

鏈路有效性檢測

心跳檢測機制分為三個層面:

1.TCP層面的心跳檢測,即TCP的Keep-Alive機制,它的作用域是整個TCP協議棧;

2.協議層的心跳檢測,主要存在於長連線協議中。例如SMPP協議;

3.應用層的心跳檢測,它主要由各業務產品通過約定方式定時給對方傳送心跳訊息實現。

心跳檢測的目的就是確認當前鏈路可用,對方活著並且能夠正常接收和傳送訊息。作為高可靠的NIO框架,Netty也提供了基於鏈路空閒的心跳檢測機制:

1.讀空閒,鏈路持續時間t沒有讀取到任何訊息;

2.寫空閒,鏈路持續時間t沒有傳送任何訊息;

3.讀寫空閒,鏈路持續時間t沒有接收或者傳送任何訊息。

零拷貝

“零拷貝”是指計算機操作的過程中, CPU不需要為資料在記憶體之間的拷貝消耗資源 。而它通常是指計算機在網路上傳送檔案時,不需要將檔案內容拷貝到使用者空間(User Space)而 直接在核心空間(Kernel Space)中傳輸到網路的方式

Netty的“零拷貝”主要體現在三個方面

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

讀取直接從“堆外直接記憶體”,不像傳統的堆記憶體和直接記憶體拷貝

ByteBufAllocator 通過ioBuffer分配堆外記憶體

Netty提供了 組合Buffer物件 ,可以聚合多個ByteBuffer物件,使用者可以 像操作一個Buffer那樣方便的對組合Buffer進行操作 ,避免了傳統通過記憶體拷貝的方式將幾個小Buffer合併成一個大的Buffer

Netty允許我們將多段資料合併為一整段虛擬資料供使用者使用,而過程中不需要對資料進行拷貝操作

組合Buffer物件,避免了記憶體拷貝

ChannelBuffer介面:Netty為需要傳輸的資料制定了統一的ChannelBuffer介面

·       使用getByte(int index)方法來實現隨機訪問

·       使用雙指標的方式實現順序訪問

·       Netty主要實現了HeapChannelBuffer,ByteBufferBackedChannelBuffer,與Zero Copy直接相關的CompositeChannelBuffer類

CompositeChannelBuffer類

CompositeChannelBuffer類的作用是將多個ChannelBuffer組成一個虛擬的ChannelBuffer來進行操作

為什麼說是虛擬的呢,因為CompositeChannelBuffer並沒有將多個ChannelBuffer真正的組合起來,而只是儲存了他們的引用,這樣就避免了資料的拷貝,實現了Zero Copy,內部實現

其中readerIndex既讀指標和writerIndex既寫指標是從AbstractChannelBuffer繼承而來的

components是一個ChannelBuffer的陣列,他儲存了組成這個虛擬Buffer的所有子Buffer

indices是一個int型別的陣列,它儲存的是各個Buffer的索引值

lastAccessedComponentId是一個int值,它記錄了最後一次訪問時的子Buffer ID

CompositeChannelBuffer實際上就是將一系列的Buffer通過陣列儲存起來,然後實現了ChannelBuffer 的介面,使得在上層看來,操作這些Buffer就像是操作一個單獨的Buffer一樣

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

Linux中的sendfile()以及Java NIO中的FileChannel.transferTo()方法都實現了零拷貝的功能,而在Netty中也通過在FileRegion中包裝了NIO的FileChannel.transferTo()方法實現了零拷貝

Netty 的 Zero-copy 體現在如下幾個個方面:

l  Netty 提供了 CompositeByteBuf 類, 它可以將多個 ByteBuf 合併為一個邏輯上的 ByteBuf, 避免了各個 ByteBuf 之間的拷貝。

l  通過 wrap 操作, 我們可以將byte[] 陣列、ByteBuf、ByteBuffer等包裝成一個 Netty ByteBuf 物件, 進而避免了拷貝操作。

l  ByteBuf 支援 slice 操作,因此可以將 ByteBuf 分解為多個共享同一個儲存區域的ByteBuf, 避免了記憶體的拷貝。

l  通過 FileRegion 包裝的FileChannel.tranferTo 實現檔案傳輸, 可以直接將檔案緩衝區的資料傳送到目標 Channel, 避免了傳統通過迴圈 write 方式導致的記