1. 程式人生 > 實用技巧 >Netty架構概述及其原理說明

Netty架構概述及其原理說明

為什麼要用Netty架構

前面一篇文章寫了java原生支援的NIO模型也是可以實現網路資料傳輸的,為什麼還有Netty出現呢?

  1. NIO的類庫和 API 繁雜,使用麻煩:需要熟練掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
  2. 要熟悉 Java 多執行緒程式設計,因為 NIO 程式設計涉及到 Reactor 模式,你必須對多執行緒和網路程式設計非常熟悉
  3. 開發工作量和難度都非常大:例如客戶端面臨斷連重連、網路閃斷、半包讀寫、失敗快取、網路擁塞和異常流
    的處理等等。
  4. NIO中有bug,至今未解決,比如Epoll Bug,它會導致 Selector 空輪詢,最終導致 CPU 100%

Netty的優點

  1. 適用於各種傳輸型別的統一 API 阻塞和非阻塞 Socket
  2. 高效能、吞吐量更高:延遲更低;減少資源消耗
  3. 發現的 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效能瓶頸的問題。

  1. Reactor 主執行緒 MainReactor 物件通過 select 監聽連線事件, 收到事件後,通過 Acceptor 處理連線事件,當 Acceptor 處理連線事件後,MainReactor 將連線分配給 SubReactor,
  2. subreactor 將連線加入到連線佇列進行監聽,並建立 handler 進行各種事件處理
  3. 當有新事件發生時, subreactor 就會呼叫對應的 handler 處理
  4. handler 通過 read 讀取資料,分發給後面的 worker 執行緒處理
  5. worker 執行緒池分配獨立的 worker 執行緒進行業務處理,並返回結果給client
優缺點

基本以上效能問題解決了,就是變成比較麻煩!

Netty

上面說了說了三種常用模式,Netty主要是基於主從 Reactors 多執行緒模型做了一定的改進。

原理圖

在這裡插入圖片描述

說明
  1. Netty 抽象出兩組執行緒池 BossGroup 專門負責接收客戶端的連線, WorkerGroup 專門負責網路的讀寫
  2. BossGroup 和 WorkerGroup 型別都是 NioEventLoopGroup
  3. NioEventLoopGroup 相當於一個事件迴圈組, 這個組中含有多個事件迴圈 ,每一個事件迴圈是 NioEventLoop
  4. NioEventLoop 表示一個不斷迴圈的執行處理任務的執行緒, 每個 NioEventLoop 都有一個 selector , 用於監聽綁
    定在其上的 socket 的網路通訊
  5. NioEventLoopGroup 可以有多個執行緒, 即可以含有多個 NioEventLoop
  6. 每個 Boss NioEventLoop 迴圈執行的步驟有 3 步
    1、 輪詢 accept 事件
    2、 處理 accept 事件 , 與 client 建立連線 , 生成 NioScocketChannel , 並將其註冊到某個 worker NIOEventLoop 上
    的 selector
    3、 處理任務佇列的任務 , 即 runAllTasks
  7. 每個 Worker NIOEventLoop 迴圈執行的步驟
    1、 輪詢 read, write 事件
    2、 處理 i/o 事件, 即 read , write 事件,在對應 NioScocketChannel 處理
    3、處理任務佇列的任務 , 即 runAllTasks
  8. 每個Worker NIOEventLoop 處理業務時,會使用pipeline(管道), pipeline 中包含了 channel , 即通過pipeline
    可以獲取到對應通道, 管道中維護了很多的 處理器

簡單來說就是BossGroup 負責請求接受,並將其註冊到WorkerGroup ,然後WorkerGroup 中的select監聽事件,有事件了就甩給下面的管道,管道里面有多執行緒可以同時處理很多業務,處理完返回就完事了、

下面的連線是一個用Netty寫的案例,供參考:https://github.com/Coderxiangyang/NettyExercise/tree/master/NettySimple

其他解釋
  1. Netty 抽象出兩組執行緒池,BossGroup 專門負責接收客戶端連線,WorkerGroup 專門負責網路讀寫操作。
  2. NioEventLoop 表示一個不斷迴圈執行處理任務的執行緒,每個 NioEventLoop 都有一個 selector,用於監聽繫結
    在其上的 socket 網路通道。
  3. 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 物件已完成,則通知指定的監聽器