1. 程式人生 > >Netty 線程模型與Reactor 模式

Netty 線程模型與Reactor 模式

建立 bmi ios inf 通用 selector 成功 黑白 nbsp

前言

Netty 的線程模型是基於NIO的Selector 構建的,使用了異步驅動的Reactor 模式來構建的線程模型,可以很好的支持成百上千的 SocketChannel 連接。由於 READ/WRITE 都是非阻塞的,可以充分提升I/O線程的運行效率 ,避免了IO阻塞導致線程掛起, 同時可以讓一個線程支持對多個客戶端的連接SocketChannel的 READ/WRITE 操作, 從根本上解決了傳統阻塞IO的一線程處理一連接的弊端。

高效率的Reactor模式

Reactor 模式

是一種為處理服務請求並發,提交到一個或者多個服務處理程序的事件設計模式。當請求抵達後,服務處理程序使用解多路分配策略,然後同步地派發這些請求至相關的請求處理程序
(來自維基百科:https://zh.wikipedia.org/wiki/反應器模式)

常見的reactor模式有以下三種

  1. 單線程reactor
  2. 多線程reactor
  3. 主從reactor

1、單線程reactor

ractor 單線程模式是指所有的I/O操作都在一個NIO線程完成,該線程的職責:

1.作為NIO服務端,接收客戶端TCP連接

2.作為NIO客戶端,向客戶端發送TCP連接

3.READ/WRITE 客戶端的請求

技術分享圖片

不過單線程的reactor 模式無法發揮多核的優勢,因此對於高並發量的系統仍然存在瓶頸,主要原因如下:

1、reactor 線程既要處理來自客戶端的連接,又要處理READ/WRITE/編碼/解碼。即便cpu 100% 也難以滿足實際場景的需求

多線程Reactor 解決了這些問題

2、多線程reactor模型

reactor 多線程的實現最大的區別是擁有一個專門用來處理實際I/O 操作是線程池

優點:

1、擁有一個Acceptor 專門用來監聽請求的I/O 類型

2、使用專門線程池可以提高acceptor的並發量,並且可以將同一個SocketChannel 放於同一個I/O 線程處理,同一個I/O線程可以處理多個SocketChannel的READ/WRITE事件

技術分享圖片

在大部分場景,該線程模型都能處理,但存在這樣一種場景:單個Acceptor 線程 可能會因為需要監聽大量的 SocketChannel 連接 或 I/O事件處理或在建立建立時需要進行安全的握手認證、黑白名單過濾,而導致出現性能瓶頸。所以這種場景下,單獨一個Accceptor 會導致性能不足,便出現了第三種線程模型,主從Reactor 模型

3、主從reactor 多線程模型

相比多線程reactor模型,主從reactor多線程模型擁有了一個獨立處理 SocketChannel 連接的線程池,當客戶端從Acceptor建立連接之後,便將該連接綁定到subreactor 線程池中的某個線程中,然後由該線程綁定客戶端感興趣的I/O事件(READ/WRITE),監聽客戶端連接請求,最後處理。

mainReactor : 監聽 ServerSocketChannel 、建立與 SocketChannel 的連接、將完成建立連接之後的Socket 交給subReactor

subReactor : 監聽SocketChannel的 I/O事件,完成編解碼、相應的業務處理(默認為CPU個數)

技術分享圖片

Netty的線程模型可以通過配置不同參數實現不同reactor的線程模型(netty4版本未發現多線程reactor版本實現方式)

1、單線程reactor

NioEventLoopGroup loopGroup = new NioEventLoopGroup(1);
ServerBootstrap b = new ServerBootstrap();
b.group(loopGroup, loopGroup)
        .channel(NioServerSocketChannel.class)
        .handler(new LoggingHandler(LogLevel.DEBUG))
        .childHandler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel ch) throws Exception {
                // xxx
            }
        });
try {
    ChannelFuture future = b.bind(8086).sync();
    future.channel().closeFuture().sync();
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    loopGroup.shutdownGracefully().sync();
}

2、主從reactor

NioEventLoopGroup mainGroup = new NioEventLoopGroup(1);
NioEventLoopGroup subGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors());
ServerBootstrap b = new ServerBootstrap();
b.group(mainGroup, subGroup)
        .channel(NioServerSocketChannel.class)
        .handler(new LoggingHandler(LogLevel.DEBUG))
        .childHandler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel ch) throws Exception {
                // xxx
            }
        });
 
try {
    ChannelFuture future = b.bind(8086).sync();
    future.channel().closeFuture().sync();
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    mainGroup.shutdownGracefully().sync();
    subGroup.shutdownGracefully().sync();
}

NioEventLoop 與 NioEventLoopGroup 介紹

技術分享圖片

1、NioEventLoopGroup 是通過實現JUC 包相關線程池的接口來達到自定義線程池的目的,而NioEventLoop 則持有一個線程,用來處理SocketChannel 建立連接之後的I/O事件。

2、在初始化 NioEventLoopGroup 時,將初始化一定數量 NioEventLoop,並將這些初始化之後的 NioEventLoops 的引用交給NioEventLoopGroup 管理

3、由接口的實現可以看出,他們都是實現了juc 線程池的頂層接口 Executor,因此都具有公共的 execute(Runable command)及 submit(Runable task) 方法,那麽這些通用的方法將為 NioEventLoopGroup 把任務交給 子的NioEventLoop處理提供標準及便利

//輪詢一個子的 NioEventLoop 進行提交處理
@Override
public Future<?> submit(Runnable task) {
    return next().submit(task);
}
......
@Override
public void execute(Runnable command) {
    next().execute(command);
}

Netty 具體線程模技術分享圖片

從整個線程模型來看 相關組件之間的是如何相互協作的

1、初始化NioEventLoopGroup , 將為ServerSocketChannel 提供一個 bossGroup 線程池,為 SockerChannel 的I/O 事件處理 提供一個workGroup

2、使用ServerBootstrap 綁定端口等相關信息,此時會初始化一個ServerSocketChannel 和 bossGroup,並且將 ServerSocketChannel 綁定到 bossGroup 中的一個NioEventLoop 中進行監聽客戶端的連接請求

3、當 Client 發起連接請求時,首先經過三次握手通過後,然後服務端被觸發,接著收到連接成功的通知(因為是異步所以是觸發)

4、ServerSocketChannel 收到連接成功的通知後,將建立好的連接交給 workGroup中的某個NioEventLoop,然後將感興趣的事件註冊到 該 NioEventLoop 持有的Selector上,等待Client 下一次請求

5、當 Client 發起 READ/WRITE 相關的請求時,則提交給NioEventLoop 進行處理

參考:

http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

Netty 線程模型與Reactor 模式