1. 程式人生 > >Netty IO執行緒模型學習總結

Netty IO執行緒模型學習總結

Netty框架的 主要執行緒是IO執行緒,執行緒模型的好壞直接決定了系統的吞吐量、併發性和安全性。

Netty的執行緒模型遵循了Reactor的基礎執行緒模型。下面我們先一起看下該模型

Reactor執行緒模型

Reactor 單執行緒模型

screenshot

單執行緒模型中所有的IO操作都在一個NIO執行緒上操作:

包含接受客戶端的請求,讀取客戶端的訊息和應答。由於使用的是非同步非阻塞的IO,所有的IO操作不會阻塞,理論上一個執行緒就可以處理所有的IO操作。

單執行緒模型適用小容量的應用。因為在高併發應用 可導致以下問題

  1. 一個執行緒同時處理成百上千的鏈路,效能上無法支撐。即使IO執行緒cpu 100%也無法滿足要求。

  2. 當NIO線層負載過重,處理速度將變慢,會導致大量的客戶端超時,重發,會更加重NIO的負載,最終導致系統大量超時

  3. 一旦IO執行緒跑飛,會導致整個系統通訊模組不可用,造成節點故障

Reactor多執行緒模型

screenshot

該模型組織了 一組執行緒進行IO的操作
特點:
1. 有專門的NIO執行緒---acceptor執行緒用於監聽伺服器,接受客戶端的TCP請求
2. 網路操作的讀寫 由一個IO執行緒池負責 負責訊息的讀取 接收 編碼和傳送
3. 一個IO執行緒可以同時處理N條鏈路,但是一條鏈路 只對應一個Io執行緒。防止併發的操作問題

適合絕大多數場景,但是對於併發百萬或者伺服器需要對客戶端握手進行安全認證,認證非常耗效能的情況,會導致效能瓶頸!

主次Reactor多執行緒模型

screenshot

接受客戶端的連線 不在是一個單獨的IO執行緒,而是一個Nio執行緒池:

Acceptor接受客戶端的請求並處理完成後,將新建的socketChannel註冊到IO執行緒池的某個執行緒上,由

他負責IO的讀寫 接編碼工作。Acceptor執行緒池僅僅負責客戶端的登入 握手 和 安全認證,一旦鏈路成

功,將鏈路註冊到後端的執行緒池的執行緒上,有他進行後續的Io操作。

Netty執行緒模型


public void bind(int port) throws Exception {
// 配置服務端的NIO執行緒組
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
// 繫結埠,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服務端監聽埠關閉
f.channel().closeFuture().sync();
} finally {
// 優雅退出,釋放執行緒池資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel arg0) throws Exception {
arg0.pipeline().addLast(new TimeServerHandler());
}
}

netty伺服器在啟動的時候,建立了兩個NIOEventLoopGroup 獨立的Reator執行緒池,一個用於接收客戶端的TCP連線,一個用於處理IO的相關的讀寫操作。

Netty執行緒模型就是在reactor模型的基礎上建立的,執行緒模型並不是一成不變的,通過啟動引數的配置,可以在三種中切換。

啟動過程,bossGroup 會選擇一個EventLoop 需要繫結serverSocketChannel 進行接收客戶端連線;處理後,將準備好的socketchnanell順利註冊到workGroup下。

netty服務端的建立過程

時序圖:
screenshot

Netty 遮蔽NIO通訊的底層細節:

  1. 首先建立ServerBootstrap,他是Netty服務端的啟動輔助類

  2. 設定並繫結Reactor執行緒池。Netty的Reactor執行緒池是EventLoopGroup,它實際就是EventLoop線 程的陣列。EventLoop的職責是處理所有註冊到本執行緒多路複用器Selector上的Channel

  3. 設定NIOserverSocketChannel. Netty通過工廠類,利用反射建立NioServerSocketChannel物件

  4. 設定TCP引數

  5. 鏈路建立的時候建立並初始化ChannelPipeline.它本質就是一個負責處理網路事件的職責鏈,負責管理和執行ChannelHandler。網路事件以事件流的形式在ChannelPipeline中流轉,由ChannelPipeline根據ChannelHandler的執行策略排程ChannelHandler的執行

    1. 繫結並啟動監聽埠
    2. 繫結埠,並啟動。將會啟動NioEventLoop負責排程和執行Selector輪詢操作,選擇準備就緒的Channel集合。當輪詢到準備就緒的Channel之後,就由Reactor執行緒NioEventLoop執行ChannelPipeline的相應方法,最終排程並執行ChannelHandler。

NioEventLoop IO執行緒淺析

做為Netty的Reactor執行緒,因為要處理網路IO讀寫,所以聚合一個多路複用器物件,它通過open獲取一個多路複用器。他的操作主要是在run方法的for迴圈中執行的。

  1. 做為bossGroup的執行緒 他需要繫結NioServerSocketChannel 來監聽客戶端的connet請求,並處理連線和校驗。
  2. 作為workGroup線層組的執行緒,需要將連線就緒的SocketChannel繫結到執行緒中,所以一個客戶端連線至對應一個執行緒,一個執行緒可以繫結多個客戶端連線。

從排程層面看,也不存在在EventLoop執行緒中 再啟動其它型別的執行緒用於非同步執行其它的任務,這樣就避免了多執行緒併發操作和鎖競爭,提升了I/O執行緒的處理和排程效能。

NioEventLoop執行緒保護

IO操作是執行緒是的核心,一旦出現故障,導致其上面的多路複用器和多個鏈路無法正常工作,因此他需要特別的保護。他在以下兩個方面做了保護處理:

  1. 謹慎處理異常

異常可能導致執行緒跑飛,會導致執行緒下的所有鏈路不可用,這時採try{}catch(Throwable) 捕獲異常,防止跑飛。出現異常後,可以恢復執行。netty的原則是 某個訊息的異常不會導致整個鏈路的不可用,某個鏈路的不可用,不能導致其他鏈路的不可用。

  1. 規避NIO BUG

screenshot

Selector.select 沒有任務執行時,可能觸發JDK的epoll BUG。這就是著名的JDK epoll BUG,JDK1.7早期版本 號稱解決了,但是據網上反饋,還有此BUG。伺服器直接表現為 IO執行緒的 CPU很高,可能達到100%,可能會導致節點故障!!!

為什麼會發生epoll Bug

screenshot

Netty的修復策略為:

  1. 對Selector的select的操作週期進行統計

  2. 對每完成一次空的select操作進行一次計數

  3. 在某週期內(如100ms)連續N此空輪詢, 說明觸發了epoll死迴圈BUG

  4. 檢測到死迴圈後,重建selector的方式讓系統恢復正常

netty採用此策略,完美避免了此BUG的發生。

參考資料:netty權威指南2