1、《Scalable IO in Java》

作者:DougLea
说到并发与Reactor不得不提大神李二狗,估计着搞java的没人会不知道这个家伙吧,想完整查看的可以去官方看他写的Scalable Io in Java,这里着重去理解分析一下他的核心思想

2、经典的通讯服务设计,如BIO

image.png
如上图,客户端连接在各自的线程里,没有数据时线程会阻塞,多个客户端连接时需要开很多线程去响应连接请求,容易造成资源浪费和大量连接时系统挂机

3、基础的Reactor响应式编程,如NIO

image.png
image.png
如上图,我们可以在NIO程序里只启动一个线程,并利用多路复用器selector(即上图中的Reactor事件,在linux里是通过EPoll来实现)来实现高效的消息服务器,一个线程就可以快速处理请求、读写、编码、解码、数据发送。

4、Worker线程池版本,如netty

image.png
如上图,netty就是基于这种模型进行优化的,上述第2点基础Reactor中,NIO只启动了一个线程去处理所有的事情,但如果Reactor中响应的读写消息事件很多,如有100000个,那消息就会有一定的延迟并且如果此时有新的连接请求来了也暂时响应不了或者有延迟,因为所有的消息均是由一个线程去处理的。
此时我们可以用多线程worker来处理reactor中的消息事件,加快消息处理和并发响应能力。

5、主从Reactors或者多主多从Reactors,如netty

image.png
如上图,我们还可以通过主从Reactors来区分事件,比如连接请求、消息读写区分在不同的Reactor里,并且还可以用不到的线程池去处理这些不同的Reactor消息事件,这样多个Reactor并发请求事件能在线程池里快速进行响应处理,而Reactor消息读写事件也可以由多个线程快速去处理。
5.1 netty便是基于这样的思想来优化的,如:
bossGroup线程池只处理连接请求:
EventLoopGroup bossGroup = new NioEventLoopGroup(2);
这里开启2个线程去处理连接请求,每个线程都会绑定自己的一个Reactors(selector)消息事件,在处理好连接请求之后把channel注册给workerGroup去响应处理就好了。

  1. // 绑定端口并且同步,生成了一个ChannelFuture异步对象
  2. ChannelFuture channelFuture = serverBootstrap.bind(bind_port).sync();

5.2 workerGroup线程池处理业务消息处理:
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
这里开启8个线程去处理消息读写业务逻辑,每个线程都会绑定自己的一个Reactors(selector)消息事件,假如现在有16个连接进来了,bossGroup的selector便会感应到,bossGroup的NioEventLoop线程便会处理这些连接请求,然后会轮询注册分配给workerGroup的8个NioEventLoop线程去处理,也就是8个NioEventLoop线程里的selector此时会各有两个channel了,当客户端1再发送消息时,线程1的selector便会感知到有消息读写,线程1此时便会开始处理消息了,所以这些线程是并发处理的。