Reactor事件驱动模型的学习

参考:添加链接描述
添加链接描述



前置:Java NIO和BIO,AIO的主要区别


reactor模型相比传统 one-pre-one-thread模型来看,把请求粒度缩小到事件维度。比如:一个读请求,包含了连接事件,读就绪事件,写就绪事件,还有编码事件,解码事件等等。这样的话,多个读请求,不太可能会同时就绪。因为人操作总有先后,那么线程就能为先就绪的读事件处理。
如果是传统IO模型,读未就绪的请求会一直占用一个线程,浪费cpu。

reactor是事件驱动模型,不是请求驱动,也不是其他驱动。
事件是比线程粒度更小的东西,优先服务事件就绪的client,充分利用(其实是压榨)cpu资源。
线程的资源是有限的(因为和cpu相关),但事件的资源几乎无限(因为和内存相关)。cpu资源其实也就几核,几十核,最多也就支持个几百个线程。
线程再多的话,就会导致线程间切换开销快速增长,机器整体吞吐量快速下降。但是事件驱动模型就不会,有限个线程支持几乎无限个事件。

事件驱动模型也是有缺点的,比如,并发量不小也不大的时候,传统IO模型对请求的响应更加及时,毕竟没有其他请求影响它。但是,reactor模型select多个相同事件的时候,while循环处理,第一个事件对应的请求1就要比最后一个处理的事件对应的请求2,响应的快那么一点点。给client的感觉就是,同时发起的请求,你比我快一些。所以,reactor模型,不适用并发量小且io数据量大的场景(文件下载功能)。


在这里插入图片描述
要启动一个Netty服务端,最小结构必须指定三类属性,分别是:
1、线程模型
2、I/O模型
3、客户端连接读、写处理逻辑,其实也可以不写,但这样的话,这个服务器就没什么意义了

有了这三者,在使用服务端引导器serverBootstrap调用bind(xxx),就可以绑定一个端口,将Netty服务端启动



整个流程抽象如下:

一定是一个服务端端口对应一个NIO线程,即所谓的reactor线程,即Netty的新连接接入线程池,它针对一个端口一定只启动一个NIO线程,即使你设置多个也没有用。

该线程只监听一个事件——OP_ACCEPT,即新连接接入事件,当触发该I/O事件后,Netty服务端的Channel底层封装的JDK的ServerSocketChannel就会接收该连接,该过程永远不会阻塞,并实例化一个Netty客户端的Channel,底层是JDK的SocketChannel,此后通过pipeline机制,迅速将新连接传递,如此一来该reactor线程就不会阻塞,可以源源不断的响应新连接,节省了大量线程,如果是传统的阻塞式网络编程,那么这里的接入线程池会启动大量线程,即一个线程对应一个网络连接,如下是Netty的一个处理流程图:
在这里插入图片描述
新连接被传递到pipeline上,本身是触发channelRead方法,即一个入站事件,最后会进入新连接接入器,在该接入器内,新连接被各种加工,然后为其分配一个新的I/O线程池里的线程,该线程专门处理当前Channel上的读写事件,至于其他耗时业务逻辑,一般都是单独开一个业务线程池处理,如此一来整个流程都不会发生无意义的阻塞。至于这些读写的数据就是通过Netty的Bytebuf组件管理的,即Netty的内存图像。

后续会反复看到该图,先看所谓的reactor模型到底是什么。
在我看来,Reactor模型本质就是一个死循环+I/O多路复用,如下demo,它的代码表现基本就是这样:

while(true){
 xxx = selector.select();
 ...
 }

因为它会源源不断的产生新的I/O事件,所以被称作反应堆(reactor)是很贴切的形容。

java的nio就是多路复用模型,普通的nio只是应用程序开启一个线程轮询扫描
多路复用io使用的是selector或者poll在内核中进行扫描,时间复杂度是O(n)。aio便是借助特定的操作系统的支持,比如Linux,使用的是epoll。并且多路复用io目的不是更快的处理,而是处理更多的请求。



常见的reactor模型有三类(这里摘抄了网络的几个图,画的很好,没必要重画了,侵删):
1、单线程模型
在这里插入图片描述
如上,整个流程中就有一个NIO线程,而且充当的是reactor角色,它即处理接入的新连接,也处理新连接上的读写事件,handler是一个个的业务处理器,它们都在一个I/O线程中执行。
看起来线程数很少很高效,但是这个模型的缺陷是其中某个handler的流程阻塞会导致其他的客户端连接上的handler都得不到执行,并且更严重的问题是:当N个handler长时间阻塞,会导致整个服务不能接收新的外部请求,因为acceptor处理单元也被阻塞了。因此单线程Reactor模型用的比较少,仅仅是理论设计。

2、多线程模型

在这里插入图片描述
多线程模型与单线程模型的区别是:多线程reactor模型的acceptor是一个单独的NIO线程。即acceptor只用于监听客户端连接请求
而具体的业务处理交给一组特定的NIO线程负责,即各个客户端连接的I/O操作交给其它的线程池,其中每个客户端连接都与线程池中的一个NIO线程绑定,因此在这个客户端连接中的所有I/O操作都是在同一个线程中完成。
客户端连接有很多,但NIO线程数比较少,因此一个NIO线程可以同时绑定到多个客户端连接中,反之不行,它们属于一对多的关系。

3、主从多线程模型
在这里插入图片描述
如果服务器需要同时处理大量客户端连接请求,或需要在客户端连接时进行一些权限的检查,那么单线程的acceptor很有可能就处理不过来,会造成大量的客户端不能连接到服务器的现象。
主从多线程模型就是在这样的情况下提出来的,它的特点是服务器端接收客户端的连接请求不再是一个线程,而是由一个独立的线程池组成,同时监听多个服务端口,这样能处理更多的新连接接入,同样的,handler处理仍然是由一个单独的NIO线程池负责。

事实上,一般情况下Reactor的多线程模型已经可以很好的工作了,主从模型用的也不多,没必要。Netty服务端编码实现就是采用了第二种,即多线程reactor模型,只需要监听一个端口,即一个服务器端口对应一个reactor线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值