Netty架構概述及其原理說明
為什麼要用Netty架構
前面一篇文章寫了java原生支援的NIO模型也是可以實現網路資料傳輸的,為什麼還有Netty出現呢?
- NIO的類庫和 API 繁雜,使用麻煩:需要熟練掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
- 要熟悉 Java 多執行緒程式設計,因為 NIO 程式設計涉及到 Reactor 模式,你必須對多執行緒和網路程式設計非常熟悉
- 開發工作量和難度都非常大:例如客戶端面臨斷連重連、網路閃斷、半包讀寫、失敗快取、網路擁塞和異常流
的處理等等。 - NIO中有bug,至今未解決,比如Epoll Bug,它會導致 Selector 空輪詢,最終導致 CPU 100%
Netty的優點
- 適用於各種傳輸型別的統一 API 阻塞和非阻塞 Socket
- 高效能、吞吐量更高:延遲更低;減少資源消耗
- 發現的 Bug 被及時修復
Netty版本
netty 版本分為 netty3.x 和 netty4.x、netty5.x,因為 Netty5 出現重大 bug,已經被官網廢棄了,目前推薦使用的是 Netty4.x 的穩定版本
常見資料請求處理模式
在此之前,先介紹幾個常用的資料請求的模式。 目前比較常見的模式有傳統阻塞 I/O 服務模型,即一個請求一個執行緒,這個不再說。Reactor 模式:根據Reactor的數量又分為幾種實現。
單 Reactor 單執行緒
原理圖:
說明:
實現應用程式通過一個阻塞物件監聽多路連線請求,Reactor 物件通過 Select 監控客戶端請求事件,收到事件後通過 Dispatch 進行分發,如果是連線請求則由 Acceptor 通過 Accept 處理連線請求,如果不是,則由Handler 來響應
優缺點
優點:模型簡單,沒有多執行緒、程序通訊、競爭的問題,全部都在一個執行緒中完成
缺點:效能問題,只有一個執行緒,無法完全發揮多核 CPU 的效能,執行緒意外終止,或者進入死迴圈,會導致整個系統通訊模組不可用,不能接收和處理外部訊息,造成節點故障
單 Reactor 多執行緒
原理圖
說明
前面跟上面單執行緒一樣,也是select監控請求,然後收到請求,將其轉發,如果是連線請求,則交給accept處理,如果不是,則交給handler處理,不同的是。這裡handler只負責響應事件,不做具體的業務處理, 通過 read 讀取資料後,會分發給後面的 worker 執行緒池的某個執行緒處理業務,worker 執行緒池會分配獨立執行緒完成真正的業務,並將結果返回給 handler,然後通過 send 將結果返回給 client
優缺點
這裡充分發揮了CPU多執行緒的優勢,也可以同時處理很多業務,但是 reactor需要 處理所有的事件的監聽和響應,並且是單執行緒,這裡在高併發會出現瓶頸。
主從 Reactor 多執行緒
原理圖
說明
跟上面不用的是,新增 MainReactor, MainReactor可以連線多個SubReactor,這就解決了一個Reactor效能瓶頸的問題。
- Reactor 主執行緒 MainReactor 物件通過 select 監聽連線事件, 收到事件後,通過 Acceptor 處理連線事件,當 Acceptor 處理連線事件後,MainReactor 將連線分配給 SubReactor,
- subreactor 將連線加入到連線佇列進行監聽,並建立 handler 進行各種事件處理
- 當有新事件發生時, subreactor 就會呼叫對應的 handler 處理
- handler 通過 read 讀取資料,分發給後面的 worker 執行緒處理
- worker 執行緒池分配獨立的 worker 執行緒進行業務處理,並返回結果給client
優缺點
基本以上效能問題解決了,就是變成比較麻煩!
Netty
上面說了說了三種常用模式,Netty主要是基於主從 Reactors 多執行緒模型做了一定的改進。
原理圖
說明
- Netty 抽象出兩組執行緒池 BossGroup 專門負責接收客戶端的連線, WorkerGroup 專門負責網路的讀寫
- BossGroup 和 WorkerGroup 型別都是 NioEventLoopGroup
- NioEventLoopGroup 相當於一個事件迴圈組, 這個組中含有多個事件迴圈 ,每一個事件迴圈是 NioEventLoop
- NioEventLoop 表示一個不斷迴圈的執行處理任務的執行緒, 每個 NioEventLoop 都有一個 selector , 用於監聽綁
定在其上的 socket 的網路通訊 - NioEventLoopGroup 可以有多個執行緒, 即可以含有多個 NioEventLoop
- 每個 Boss NioEventLoop 迴圈執行的步驟有 3 步
1、 輪詢 accept 事件
2、 處理 accept 事件 , 與 client 建立連線 , 生成 NioScocketChannel , 並將其註冊到某個 worker NIOEventLoop 上
的 selector
3、 處理任務佇列的任務 , 即 runAllTasks - 每個 Worker NIOEventLoop 迴圈執行的步驟
1、 輪詢 read, write 事件
2、 處理 i/o 事件, 即 read , write 事件,在對應 NioScocketChannel 處理
3、處理任務佇列的任務 , 即 runAllTasks - 每個Worker NIOEventLoop 處理業務時,會使用pipeline(管道), pipeline 中包含了 channel , 即通過pipeline
可以獲取到對應通道, 管道中維護了很多的 處理器
簡單來說就是BossGroup 負責請求接受,並將其註冊到WorkerGroup ,然後WorkerGroup 中的select監聽事件,有事件了就甩給下面的管道,管道里面有多執行緒可以同時處理很多業務,處理完返回就完事了、
下面的連線是一個用Netty寫的案例,供參考:https://github.com/Coderxiangyang/NettyExercise/tree/master/NettySimple
其他解釋
- Netty 抽象出兩組執行緒池,BossGroup 專門負責接收客戶端連線,WorkerGroup 專門負責網路讀寫操作。
- NioEventLoop 表示一個不斷迴圈執行處理任務的執行緒,每個 NioEventLoop 都有一個 selector,用於監聽繫結
在其上的 socket 網路通道。 - NioEventLoop 內部採用序列化設計,從訊息的讀取->解碼->處理->編碼->傳送,始終由 IO 執行緒 NioEventLoop
負責
1、 NioEventLoopGroup 下包含多個 NioEventLoop
2、 每個 NioEventLoop 中包含有一個 Selector,一個 taskQueue
3、 每個 NioEventLoop 的 Selector 上可以註冊監聽多個 NioChannel
4、 每個 NioChannel 只會繫結在唯一的 NioEventLoop 上
5、 每個 NioChannel 都繫結有一個自己的 ChannelPipeline
Netty非同步模型
Netty 中的 I/O 操作是非同步的,包括 Bind、Write、Connect 等操作會簡單的返回一個 ChannelFuture。呼叫者並不能立刻獲得結果,而是通過 Future-Listener 機制,使用者可以方便的主動獲取或者通過通知機制獲得IO 操作結果,
Netty 的非同步模型是建立在 future 和 callback 的之上的。callback 就是回撥。重點說 Future,它的核心思想是:假設一個方法 fun,計算過程可能非常耗時,等待 fun 返回顯然不合適。那麼可以在呼叫 fun 的時候,立馬返回一個 Future,後續可以通過 Future 去監控方法 fun 的處理過程(即 : Future-Listener 機制)
Future-Listener 機制)
當 Future 物件剛剛建立時,處於非完成狀態,呼叫者可以通過返回的 ChannelFuture 來獲取操作執行的狀態,
註冊監聽函式來執行完成後的操作。
常見操作:
1、通過 isDone 方法來判斷當前操作是否完成;
2、 通過 isSuccess 方法來判斷已完成的當前操作是否成功;
3、 通過 getCause 方法來獲取已完成的當前操作失敗的原因;
4、 通過 isCancelled 方法來判斷已完成的當前操作是否被取消;
5、 通過 addListener 方法來註冊監聽器,當操作已完成(isDone 方法返回完成),將會通知指定的監聽器;如果
Future 物件已完成,則通知指定的監聽器