Netty IO線程模型學習總結

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

Netty的線程模型遵循了Reactor的基礎線程模型。如下咱們先一塊兒看下該模型後端

Reactor線程模型

Reactor 單線程模型

screenshot

單線程模型中所有的IO操做都在一個NIO線程上操做:數組

包括接受client的請求,讀取client的消息和應答。由於使用的是異步非堵塞的IO,所有的IO操做不會堵塞。理論上一個線程就可以處理所有的IO操做。安全

單線程模型適用小容量的應用。網絡

因爲在高併發應用 可致使下面問題多線程

  1. 一個線程同一時候處理成百上千的鏈路,性能上沒法支撐。併發

    即便IO線程cpu 100%也沒法知足要求。框架

  2. 當NIO線層負載太重,處理速度將變慢,會致使大量的client超時,重發,會更加劇NIO的負載。終於致使系統大量超時異步

  3. 一旦IO線程跑飛,會致使整個系統通信模塊不可用,形成節點故障socket

Reactor多線程模型

screenshot

該模型組織了 一組線程進行IO的操做
特色:
1. 有專門的NIO線程---acceptor線程用於監聽server,接受client的TCP請求
2. 網絡操做的讀寫 由一個IO線程池負責 負責消息的讀取 接收 編碼和發送
3. 一個IO線程可以同一時候處理N條鏈路。但是一條鏈路 僅僅相應一個Io線程。防止併發的操做問題

適合絕大多數場景,但是對於併發百萬或者server需要對client握手進行安全認證,認證很耗性能的狀況,會致使性能瓶頸。

主次Reactor多線程模型

screenshot

接受client的鏈接 不在是一個單獨的IO線程,而是一個Nio線程池:

Acceptor接受client的請求並處理完畢後,將新建的socketChannel註冊到IO線程池的某個線程上,由

他負責IO的讀寫 接編碼工做。

Acceptor線程池只負責client的登陸 握手 和 安全認證,一旦鏈路成

功,將鏈路註冊到後端的線程池的線程上,有他進行興許的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());
// 綁定port,同步等待成功
ChannelFuture f = b.bind(port).sync()。
// 等待服務端監聽port關閉
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());
}
}

nettyserver在啓動的時候,建立了兩個NIOEventLoopGroup 獨立的Reator線程池,一個用於接收client的TCP鏈接,一個用於處理IO的相關的讀寫操做。

Netty線程模型就是在reactor模型的基礎上創建的,線程模型並不是一成不變的,經過啓動參數的配置,可以在三種中切換。

啓動過程,bossGroup 會選擇一個EventLoop 需要綁定serverSocketChannel 進行接收client鏈接;處理後,將準備好的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. 綁定並啓動監聽port
    2. 綁定port,並啓動。將會啓動NioEventLoop負責調度和運行Selector輪詢操做,選擇準備就緒的Channel集合。當輪詢到準備就緒的Channel以後,就由Reactor線程NioEventLoop運行ChannelPipeline的對應方法。終於調度並運行ChannelHandler。

NioEventLoop IO線程淺析

作爲Netty的Reactor線程,因爲要處理網絡IO讀寫,因此聚合一個多路複用器對象,它經過open獲取一個多路複用器。他的操做主要是在run方法的for循環中運行的。

  1. 作爲bossGroup的線程 他需要綁定NioServerSocketChannel 來監聽client的connet請求,並處理鏈接和校驗。
  2. 做爲workGroup線層組的線程。需要將鏈接就緒的SocketChannel綁定到線程中。因此一個client鏈接至相應一個線程,一個線程可以綁定多個client鏈接。

從調度層面看。也不存在在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。

server直接表現爲 IO線程的 CPU很是高,可能達到100%,可能會致使節點故障!

!!

爲何會發生epoll Bug

screenshot

Netty的修復策略爲:

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

  2. 對每完畢一次空的select操做進行一次計數

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

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

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


參考資料:netty權威指南2

相關文章
相關標籤/搜索