Netty中的三种Reactor(反应堆)
目录:
Reactor(反应堆)和Proactor(前摄器)
《I/O模型之三:两种高性能 I/O 设计模式 Reactor 和 Proactor》
《【转】第8章 前摄器(Proactor):用于为异步事件多路分离和分派处理器的对象行为模式》
《Java NIO系列教程(八)JDK AIO编程》-- java AIO的proactor模式
《Java NIO系列教程(七) selector原理 Epoll版的Selector》--java NIO的Reactor模式
Netty的I/O线程NioEventLoop由于聚合了多路复用器Selector,可以同时并发处理成百上千个客户端SocketChannel。由于读写操作都是非阻塞的,这就可以充分提升I/O线程的运行效率,避免由频繁的I/O阻塞导致的线程挂起。另外,由于Netty采用了异步通信模式,一个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
高效的Reactor线程模型
常见的Reactor线程模型有三种,分别如下:
- Reactor单线程模型;
- Reactor多线程模型;
- 主从Reactor多线程模型;
Netty是典型的Reactor模型结构,关于Reactor的详尽阐释,可参考POSA2,这里不做概念性的解释。而应用Java NIO构建Reactor模式,Doug Lea(就是那位让人无限景仰的大爷)在“Scalable IO in Java”中给了很好的阐述。这里截取其PPT中经典的图例说明 Reactor模式的典型实现:
1、Reactor单线程模型
Reactor单线程模型,指的是所有的I/O操作都在同一个NIO线程上面完成,NIO线程的职责如下:
- 作为NIO服务端,接收客户端的TCP连接;
- 作为NIO客户端,向服务端发起TCP连接;
- 读取通信对端的请求或者应答消息;
- 向通信对端发送消息请求或者应答消息;
Reactor线程是个多面手,负责多路分离套接字,Accept新连接,并分派请求到处理器链中。该模型 适用于处理器链中业务处理组件能快速完成的场景。不过,这种单线程模型不能充分利用多核资源,所以实际使用的不多。

对于一些小容量应用场景,可以使用单线程模型,但是对于高负载、大并发的应用却不合适,主要原因如下:
- 一个NIO线程同时处理成百上千的链路,性能上无法支撑。即便NIO线程的CPU负荷达到100%,也无法满足海量消息的编码、解码、读取和发送;
- 当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往进行重发,这更加重了NIO线程的负载,最终导致大量消息积压和处理超时,NIO线程会成为系统的性能瓶颈;
- 可靠性问题。一旦NIO线程意外跑飞,或者进入死循环,会导致整个系统通讯模块不可用,不能接收和处理外部信息,造成节点故障。
为了解决这些问题,演进出了Reactor多线程模型,下面我们一起学习下Reactor多线程模型。
2、Reactor多线程模型
Reactor多线程模型与单线程模型最大区别就是有一组NIO线程处理I/O操作,它的特点如下:
- 有一个专门的NIO线程--acceptor新城用于监听服务端,接收客户端的TCP连接请求;
- 网络I/O操作--读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送;
- 1个NIO线程可以同时处理N条链路,但是1个链路只对应1个NIO线程,防止发生并发操作问题。

在绝大多数场景下,Reactor多线程模型都可以满足性能需求;但是,在极特殊应用场景中,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端并发连接,或者服务端需要对客户端的握手信息进行安全认证,认证本身非常损耗性能。这类场景下,单独一个Acceptor线程可能会存在性能不足问题,为了解决性能问题,产生了第三种Reactor线程模型--主从Reactor多线程模型。
3、主从Reactor多线程模型
特点是:服务端用于接收客户端连接的不再是1个单独的NIO线程,而是一个独立的NIO线程池。Acceptor接收到客户端TCP连接请求处理完成后(可能包含接入认证等),将新创建的SocketChannel注册到I/O线程池(sub reactor线程池)的某个I/O线程上,由它负责SocketChannel的读写和编解码工作。
Acceptor线程池只用于客户端的登录、握手和安全认证,一旦链路建立成功,就将链路注册到后端subReactor线程池的I/O线程上,有I/O线程负责后续的I/O操作。
第三种模型比起第二种模型,是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接,并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,读写网 络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同。

NioEventLoopGroup 与 Reactor 线程模型的对应
Netty的线程模型并发固定不变,通过在启动辅助类中创建不同的EventLoopGroup实例并进行适当的参数配置,就可以支持上述三种Reactor线程模型。
Netty单线程模型服务端代码示例如下:
/**
* Netty单线程模型服务端代码示例
* @param port
*/
public void bind(int port) {
EventLoopGroup reactorGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(reactorGroup, reactorGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("http-codec", new HttpServerCodec());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
//后面代码省略
}
}); Channel ch = b.bind(port).sync().channel();
ch.closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reactorGroup.shutdownGracefully();
}
}
Netty多线程模型代码如下:
/**
* Netty多线程模型代码
* @param port
*/
public void bind2(int port) {
EventLoopGroup acceptorGroup = new NioEventLoopGroup(1);
NioEventLoopGroup ioGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(acceptorGroup, ioGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("http-codec", new HttpServerCodec());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
//后面代码省略
}
}); Channel ch = b.bind(port).sync().channel();
ch.closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
acceptorGroup.shutdownGracefully();
ioGroup.shutdownGracefully();
}
}
Netty主从线程模型代码如下:
/**
* Netty主从线程模型代码
* @param port
*/
public void bind3(int port) {
EventLoopGroup acceptorGroup = new NioEventLoopGroup();
NioEventLoopGroup ioGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(acceptorGroup, ioGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("http-codec", new HttpServerCodec());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
//后面代码省略
}
}); Channel ch = b.bind(port).sync().channel();
ch.closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
acceptorGroup.shutdownGracefully();
ioGroup.shutdownGracefully();
}
}
说完Reacotr模型的三种形式,那么Netty是哪种呢?其实,我还有一种Reactor模型的变种没说,那就是去掉线程池的第三种形式的变种,这也 是Netty NIO的默认模式。在实现上,Netty中的Boss类充当mainReactor,NioWorker类充当subReactor(默认 NioWorker的个数是Runtime.getRuntime().availableProcessors())。在处理新来的请求 时,NioWorker读完已收到的数据到ChannelBuffer中,之后触发ChannelPipeline中的ChannelHandler流。
Netty是事件驱动的,可以通过ChannelHandler链来控制执行流向。因为ChannelHandler链的执行过程是在 subReactor中同步的,所以如果业务处理handler耗时长,将严重影响可支持的并发数。这种模型适合于像Memcache这样的应用场景,但 对需要操作数据库或者和其他模块阻塞交互的系统就不是很合适。Netty的可扩展性非常好,而像ChannelHandler线程池化的需要,可以通过在 ChannelPipeline中添加Netty内置的ChannelHandler实现类–ExecutionHandler实现,对使用者来说只是 添加一行代码而已。对于ExecutionHandler需要的线程池模型,Netty提供了两种可 选:1) MemoryAwareThreadPoolExecutor 可控制Executor中待处理任务的上限(超过上限时,后续进来的任务将被阻 塞),并可控制单个Channel待处理任务的上限;2) OrderedMemoryAwareThreadPoolExecutor 是 MemoryAwareThreadPoolExecutor 的子类,它还可以保证同一Channel中处理的事件流的顺序性,这主要是控制事件在异步处 理模式下可能出现的错误的事件顺序,但它并不保证同一Channel中的事件都在一个线程中执行(通常也没必要)。一般来 说,OrderedMemoryAwareThreadPoolExecutor 是个很不错的选择,当然,如果有需要,也可以DIY一个。
Netty中的三种Reactor(反应堆)的更多相关文章
- Netty如何支持三种Reactor
参考文献:极客时间傅健老师的<Netty源码剖析与实战>Talk is cheap.show me the code! 什么是Reactor及三种版本 反应堆设计模式(Reactor pa ...
- Netty Reator(三)Reactor 模型
Netty Reator(三)Reactor 模型 Netty 系列目录 (https://www.cnblogs.com/binarylei/p/10117436.html) 本文介绍 DC Sch ...
- Java三大框架之——Hibernate中的三种数据持久状态和缓存机制
Hibernate中的三种状态 瞬时状态:刚创建的对象还没有被Session持久化.缓存中不存在这个对象的数据并且数据库中没有这个对象对应的数据为瞬时状态这个时候是没有OID. 持久状态:对象经过 ...
- Asp.Net中的三种分页方式
Asp.Net中的三种分页方式 通常分页有3种方法,分别是asp.net自带的数据显示空间如GridView等自带的分页,第三方分页控件如aspnetpager,存储过程分页等. 第一种:使用Grid ...
- httpClient中的三种超时设置小结
httpClient中的三种超时设置小结 本文章给大家介绍一下关于Java中httpClient中的三种超时设置小结,希望此教程能给各位朋友带来帮助. ConnectTimeoutExceptio ...
- MySQL buffer pool中的三种链
三种page.三种list.LRU控制调优 一.innodb buffer pool中的三种页 1.free page:从未用过的页 2.clean page:干净的页,数据页的数据和磁盘一致 3.d ...
- 研究分析JS中的三种逻辑语句
JS中的三种逻辑语句:顺序.分支和循环语句. 一.顺序语句 代码规范如下:1. <script type="text/javascript"> var a = 10; ...
- JavaScript中的三种弹出对话框
学习过js的小伙伴会发现,我们在一些实例中用到了alert()方法.prompt()方法.prompt()方法,他们都是在屏幕上弹出一个对话框,并且在上面显示括号内的内容,使用这种方法使得页面的交互性 ...
- .net core 注入中的三种模式:Singleton、Scoped 和 Transient
从上篇内容不如题的文章<.net core 并发下的线程安全问题>扩展认识.net core注入中的三种模式:Singleton.Scoped 和 Transient 我们都知道在 Sta ...
随机推荐
- ZIP压缩包加密破解
python多线程破解zip文件,废话不多说直接上代码 # -*- coding: UTF-8 -*- #使用多线程和接受参数的形式去破解指定的zip文件 #python3 zip_file_cack ...
- centos安装桌面,下面的几个包缺一不可
yum groupinstall “X window system” yum groupinstall “Desktop” yum groupinstall “Chinese Support” 不然的 ...
- Newtonsoft.Json 概述
有时候,在前后台数据交互或者APP与后台交互的时候,我们通常会使用Json进行数据交互,为此会使用到Newtonsoft.Json.dll 这个类库,这个类库非微软官方,但是下载量已经超过了数十万次, ...
- 「CodeForces 581D」Three Logos
BUPT 2017 Summer Training (for 16) #3A 题意 给你三个矩形,需要不重叠不留空地组成一个正方形.不存在输出-1,否则输出边长和这个正方形(A,B,C表示三个不同矩形 ...
- 一种导致 emwin 中 EDIT 控件不显示的情况
@2018-12-11 [小记] 设计界面中使用了 EDIT 控件,但在其初始化语句中误使用了 text-color 属性API,导致了控件 EDIT 中的 Text 无法显示,具体如下 hItem ...
- 一种使用 emwin 绘制图片的方法
@2018-12-10 [小记] 使用官方 <GUIBuilder.exe> 软件里的 Image 控件,注意格式为 .bmp,这种方式是将图片数据直接转为十六进制数据存储至静态区 具体使 ...
- luogu1397 [NOI2013]矩阵游戏 (等比数列求和)
一个比较显然的等比数列求和,但有一点问题就是n和m巨大.. 考虑到他们是在幂次上出现,所以可以模上P-1(费马小定理) 但是a或c等于1的时候,不能用等比数列求和公式,这时候就要乘n和m,又要变成模P ...
- GCD HDU - 1695(容斥原理)
要求从满足gcd(x, y) = k的对数,其中x属于[1, n], y属于[1, m] gcd(x, y) = k ==>gcd(x/k, y/k) =1 x/k属于[1, n/k], y/k ...
- empty() 与 html("") 的区别
empty,首先循环给后代元素移除绑定.清除jquery给此dom的cache,然后循环removeFirstChild. 而html(''),则是简单暴力的设置innerHTML = ''; 查看文 ...
- P3486 [POI2009]KON-Ticket Inspector
啊!这题做的真是爽!除了DP这个方法是有提示的之外,这题居然没有题解,哈哈哈嘿嘿嘿.很自豪的说:全是我自己独立解出来的一道题,包括设计状态,推倒(☺)转移方程,最后记录路径. 好了,首先,我们发现这题 ...