Netty框架的 主要線程是IO線程。線程模型的好壞直接決定了系統的吞吐量、併發性和安全性。react
Netty的線程模型遵循了Reactor的基礎線程模型。如下咱們先一塊兒看下該模型後端
單線程模型中所有的IO操做都在一個NIO線程上操做:數組
包括接受client的請求,讀取client的消息和應答。由於使用的是異步非堵塞的IO,所有的IO操做不會堵塞。理論上一個線程就可以處理所有的IO操做。安全
單線程模型適用小容量的應用。網絡
因爲在高併發應用 可致使下面問題多線程
一個線程同一時候處理成百上千的鏈路,性能上沒法支撐。併發
即便IO線程cpu 100%也沒法知足要求。框架
當NIO線層負載太重,處理速度將變慢,會致使大量的client超時,重發,會更加劇NIO的負載。終於致使系統大量超時異步
一旦IO線程跑飛,會致使整個系統通信模塊不可用,形成節點故障socket
該模型組織了 一組線程進行IO的操做
特色:
1. 有專門的NIO線程---acceptor線程用於監聽server,接受client的TCP請求
2. 網絡操做的讀寫 由一個IO線程池負責 負責消息的讀取 接收 編碼和發送
3. 一個IO線程可以同一時候處理N條鏈路。但是一條鏈路 僅僅相應一個Io線程。防止併發的操做問題
適合絕大多數場景,但是對於併發百萬或者server需要對client握手進行安全認證,認證很耗性能的狀況,會致使性能瓶頸。
接受client的鏈接 不在是一個單獨的IO線程,而是一個Nio線程池:
Acceptor接受client的請求並處理完畢後,將新建的socketChannel註冊到IO線程池的某個線程上,由
他負責IO的讀寫 接編碼工做。
Acceptor線程池只負責client的登陸 握手 和 安全認證,一旦鏈路成
功,將鏈路註冊到後端的線程池的線程上,有他進行興許的Io操做。
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 屏蔽NIO通訊的底層細節:
首先建立ServerBootstrap,他是Netty服務端的啓動輔助類
設置並綁定Reactor線程池。
Netty的Reactor線程池是EventLoopGroup,它實際就是EventLoop線 程的數組。
EventLoop的職責是處理所有註冊到本線程多路複用器Selector上的Channel
設置NIOserverSocketChannel. Netty經過工廠類,利用反射建立NioServerSocketChannel對象
設置TCP參數
鏈路創建的時候建立並初始化ChannelPipeline.它本質就是一個負責處理網絡事件的職責鏈,負責管理和運行ChannelHandler。
網絡事件以事件流的形式在ChannelPipeline中流轉,由ChannelPipeline依據ChannelHandler的運行策略調度ChannelHandler的運行
作爲Netty的Reactor線程,因爲要處理網絡IO讀寫,因此聚合一個多路複用器對象,它經過open獲取一個多路複用器。他的操做主要是在run方法的for循環中運行的。
從調度層面看。也不存在在EventLoop線程中 再啓動其餘類型的線程用於異步運行其餘的任務。這樣就避免了多線程併發操做和鎖競爭,提高了I/O線程的處理和調度性能。
IO操做是線程是的核心,一旦出現問題,致使其上面的多路複用器和多個鏈路沒法正常工做。所以他需要特別的保護。
他在下面兩個方面作了保護處理:
異常可能致使線程跑飛。會致使線程下的所有鏈路不可用,這時採try{}catch(Throwable) 捕獲異常,防止跑飛。出現異常後,可以恢復運行。netty的原則是 某個消息的異常不會致使整個鏈路的不可用,某個鏈路的不可用。不能致使其它鏈路的不可用。
Selector.select 沒有任務運行時,可能觸發JDK的epoll BUG。這就是著名的JDK epoll BUG,JDK1.7早期版本號 號稱攻克了。但是據網上反饋,還有此BUG。
server直接表現爲 IO線程的 CPU很是高,可能達到100%,可能會致使節點故障!
!!
爲何會發生epoll Bug
Netty的修復策略爲:
對Selector的select的操做週期進行統計
對每完畢一次空的select操做進行一次計數
在某週期內(如100ms)連續N此空輪詢, 說明觸發了epoll死循環BUG
檢測到死循環後,重建selector的方式讓系統恢復正常
netty採用此策略,完美避免了此BUG的發生。
參考資料:netty權威指南2