Reactor
单 Reactor 单进程
我们来看看「单 Reactor 单进程」的方案示意图:
可以看到进程里有 Reactor、Acceptor、Handler 这三个对象:
Reactor 对象的作用是监听和分发事件; Acceptor 对象的作用是获取连接; Handler 对象的作用是处理业务;
接下来,介绍下「单 Reactor 单进程」这个方案:
Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型; 如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件; 如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应; Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。
单 Reactor 单进程的方案因为全部工作都在同一个进程内完成,所以实现起来比较简单,不需要考虑进程间通信,也不用担心多进程竞争。
但是,这种方案存在 2 个缺点:
因为只有一个进程,无法充分利用 多核 CPU 的性能; Handler 对象在业务处理时,整个进程是无法处理其他连接的事件的,如果业务处理耗时比较长,那么就造成响应的延迟;
所以,单 Reactor 单进程的方案不适用计算机密集型的场景,只适用于业务处理非常快速的场景。
单 Reactor 多线程
先来看看「单 Reactor 多线程」方案的示意图如下:
详细说一下这个方案:
Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型; 如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件; 如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应; Handler 对象不再负责业务处理,只负责数据的接收和发送,Handler 对象通过 read 读取到数据后,会将数据发给子线程里的 Processor 对象进行业务处理; 子线程里的 Processor 对象就进行业务处理,处理完后,将结果发给主线程中的 Handler 对象,接着由 Handler 通过 send 方法将响应结果发送给 client;
单 Reator 多线程的方案优势在于能够充分利用多核 CPU ,那既然引入多线程,那么自然就带来了多线程竞争资源的问题。要避免多线程由于竞争共享资源而导致数据错乱的问题,就需要在操作共享资源前加上互斥锁,以保证任意时间里只有一个线程在操作共享资源,待该线程操作完释放互斥锁后,其他线程才有机会操作共享数据。
另外,「单 Reactor」的模式还有个问题,因为一个 Reactor 对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方。
多 Reactor 多线程
要解决「单 Reactor」的问题,就是将「单 Reactor」实现成「多 Reactor」,多 Reactor 多线程方案的示意图如下:
方案详细说明如下:
主线程中的 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 对象中的 accept 获取连接,将新的连接分配给某个子线程; 子线程中的 SubReactor 对象将 MainReactor 对象分配的连接加入 select 继续进行监听,并创建一个 Handler 用于处理连接的响应事件。 如果有新的事件发生时,SubReactor 对象会调用当前连接对应的 Handler 对象来进行响应。 Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。
Proactor
前面提到的 Reactor 是非阻塞同步 网络模式,而 Proactor 是异步 网络模式。
下图为 Proactor 模式的示意图:
Reactor和Proactor的区别
Reactor 是非阻塞同步网络模式,感知的是就绪可读写事件 。在每次感知到有事件发生(比如可读就绪事件)后,就需要应用进程主动调用 read 方法来完成数据的读取,也就是要应用进程主动将 socket 接收缓存中的数据读到应用进程内存中,这个过程是同步的,读取完数据后应用进程才能处理数据。Proactor 是异步网络模式, 感知的是已完成的读写事件 。在发起异步读写请求时,需要传入数据缓冲区的地址(用来存放结果数据)等信息,这样系统内核才可以自动帮我们把数据的读写工作完成,这里的读写工作全程由操作系统来做,并不需要像 Reactor 那样还需要应用进程主动发起 read/write 来读写数据,操作系统完成读写工作后,就会通知应用进程直接处理数据。