Netty 線程模型與Reactor 模式
前言
Netty 的線程模型是基於NIO的Selector 構建的,使用了異步驅動的Reactor 模式來構建的線程模型,可以很好的支持成百上千的 SocketChannel 連接。由於 READ/WRITE 都是非阻塞的,可以充分提升I/O線程的運行效率 ,避免了IO阻塞導致線程掛起, 同時可以讓一個線程支持對多個客戶端的連接SocketChannel的 READ/WRITE 操作, 從根本上解決了傳統阻塞IO的一線程處理一連接的弊端。
高效率的Reactor模式
Reactor 模式
是一種為處理服務請求並發,提交到一個或者多個服務處理程序的事件設計模式。當請求抵達後,服務處理程序使用解多路分配策略,然後同步地派發這些請求至相關的請求處理程序(來自維基百科:https://zh.wikipedia.org/wiki/反應器模式)
常見的reactor模式有以下三種
- 單線程reactor
- 多線程reactor
- 主從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 模式