以下的图片都来自Doug lea大神的《Scalable IO in Java》,也推荐大家去阅读下,就是对英文不好的小伙伴不太友好了,大家也可以看我的翻译版本,就是有些翻译的不太好,将就着看哈哈:Doug lea《Scalable IO in Java》翻译
系列文章
- 你知道都有哪些I/O模型吗?
- Java NIO三大角色Channel、Buffer、Selector
- Doug lea《Scalable IO in Java》翻译
- Reactor模型你知道都有哪些吗?
- Netty服务端创建源码流程解析
- EventLoopGroup到底是个啥?
- 未完待续…
创作不易,如果对您有帮助,麻烦辛苦下小手点个关注,有任何问题都可以私信交流哈。
祝您虎年虎虎生威。
单Reactor 单线程模型
最基础的Reactor模型,指的是所有的I/O操作都在同一个NIO线程上完成。下图为线程模型:
Reactor模型使用的是异步非阻塞I/O,所有的I/O操作都不会导致阻塞,理论上一个县城可以独立处理所有IO操作,例如:通过Acceptor接收客户端的TCP连接请求消息,当连接建立完成后,通过Dispatch将对应的ByteBuffer派发到指定的Handler上进行消息的解码。用户线程消息编码后通过NIO线程发送给客户端。
适用场景:适用于处理器链中业务处理组件能快速完成的场景
缺点:对于高负载,大并发的应用场景不适合, 原因如下:
- 一个NIO线程线程同时处理成百上千个连接,性能上无法支撑
- 当NIO线程负载过重后,处理速度将变慢,这回导致大量客户端连接超时,消息挤压和处理超时,最终称为系统的性能瓶颈
- 可靠性问题,一旦NIO线程意外died,会导致整个系统通信模块不可用,不能接收和处理消息
基于上边的这些问题,演进出了单Reactor多线程模型。
单Reactor 多线程模型
特点:
- 有专门一个NIO线程——Acceptor线程用于监听服务端,接收客户端的TCP连接请求
- 网络I/O操作——读、写等由一个NIO线程池负责,线程池可以使用JDK的线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送响应
- 一个NIO线程可用同时处理N条链路,但是一个连接只对应一个NIO线程,防止发生并发操作问题
这种单Reactor多处理线程的模型在绝大多数场景下可用满足性能需求,但是在个别特殊场景中,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题,比如服务端需要对客户端进行安全认证,认证的过程又非常损耗性能,那就会出单点性能不足问题,因此又演进出了多Reactor多处理线程模型。
多Reactor 多线程模型
这种模型又被称为 “主从Reactor多线程模型”。
第二种模型将事件处理放到了线程池中进行多线程处理,第三种模型是将Reactor的职责进行了划分,服接收客户端连接的不再是一个单独的NIO线程,而是一个独立的NIO线程池,分为了两部分:
- mainReactor:负责监听ServerSocketChannel,用来处理客户端新连接的建立,并将建立的客户端的SocketChannel指定注册给subReactor
- subReactor:维护自己的Selector,基于mainRactor建立的客户端的SocketChannel多路分离IO读写事件,读写网络数据,对于业务处理的功能,扔给worker线程池来完成
Netty的Reactor模型
客户端
EventLoopGroup group = new NioEventLoopGroup();
//这里使用的是Bootstrap,服务端使用的是ServerBootstrap
Bootstrap b = new Bootstrap();
- Netty NIO客户端来说,仅创建一个EventLoopGroup
- 一个EventLoop可以对应一个Reactor,EventLoopGroup可以理解为Reactor的分组
- 一个Bootstrap的启动只能发起对一个远程地址的连接,所以只会使用一个NIO Selector,也就是说仅适用一个Reactor,即使,我们在声明使用一个EventLoopGroup,该EventLoopGroup也只会分配一个EventLoop对IO事件进行处理
- 如果一个业务线程使用Netty NIO客户端,可以认为是“单Reactor单线程模型”
- 如果有多个业务线程使用Netty NIO客户端,可以任务是“单Reactor多线程模型”
- 同时我们可以创建多个Netty NIO客户端来实现“多Reactor多线程模型”
服务端
// 创建两个 EventLoopGroup 对象
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 创建 boss 线程组 用于服务端接受客户端的连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 创建 worker 线程组 用于进行 SocketChannel 的数据读写
// 创建 ServerBootstrap 对象
ServerBootstrap b = new ServerBootstrap();
// 设置使用的 EventLoopGroup
b.group(bossGroup, workerGroup);
- 对于服务端,创建两个EventLoopGroup
- bossGroup对应mainReactor,用于服务端接受客户端的连接,同时传入了线程数为1,表示只使用1个Reactor,那能不能boosGroup配置多个线程呢?是可以的,但是没有意义,一个服务端只能绑定一个端口,也就只能使用一个Selector处理客户端连接事件,即使配置多个线程,实际使用的也就1个
- workerGroup对应subReactor,用于进行SocketChannel的数据读写
- 如果EventLoopGroup未设置线程个数,默认为CPU核心数 * 2.
大家好,我是壹氿,感谢各位小伙伴点赞、收藏和评论,文章持续更新,我们下期再见!
也可以加我的个人VX交流沟通:lhj502819,一起努力冲击大厂,另外有很多学习以及面试的材料提供给大家。