业务开发同学只关心业务处理流程。但是我们开发的程序都是运行服务端server上,服务端server接收到IO请求后,是如何处理请求并最终进入业务流程的呢?这里不得不提到reactor反应堆模型。reactor反应堆模型来源于大师Doug Lea在 《Sacalable io in java》中的设计。nginx tomcat redis nodejs dubbo netty 等软件的网络处理模型都是用的reactor反应堆模型。
前置知识
一般所有的网络服务,一般分为如下几个步骤:
C10K 问题是这样的:如何在一台物理机上同时服务 10000 个用户?这里 C 表示并发,10K 等于 10000。C10K问题早已解决,现在面临的是C10M问题。

一次完整的网络IO流程
读数据时,如果内核缓冲区没有数据,执行read()操作的线程会挂起在这里,不能做其他的事情
IO多路复用是一种高效的IO模型,它的特点是可以同时监控多个文件描述符,提高了应用程序对输入输出操作的管理能力。当用户进程使用select、poll或epoll等系统调用时,它会将需要监控的文件描述符传递给内核,然后内核会在所有文件描述符中寻找就绪的文件描述符,并返回给用户进程。用户进程再根据就绪的文件描述符进行相应的读写操作。
IO多路复用的优点是可以使用一个线程或进程来处理多个文件描述符,避免了多线程或多进程的开销,提高了系统的并发性和可伸缩性。IO多路复用的缺点是需要额外的系统调用来管理文件描述符,而且在数据拷贝阶段仍然是阻塞的。IO多路复用比较适合网络编程,比如服务器端的并发处理。


TPC是Thread Per Connection的缩写,其含义是指每次有新的连接就新建一个线程去专门处理这个连接的请求。
缺点:

使用场景:客户端的数量有限,业务处理非常快速,比如 Redis ,6.0版本前的redis采用此方案
所有的线程都在同一个线程中循环处理。如果某个请求耗时很长,那么其他的请求只能阻塞等待。

为了解决单reactor单线程模型的缺陷,多线程模型应运而生。多线程模型引入worker线程池来处理请求的业务逻辑。
绝大部分请求的耗时都是在process这个阶段,因此引入worker线程池对性能的改善十分有效。
当然,这种模型也有明显缺点,连接建立、IO 事件读取以及事件分发完全有单线程处理;比如当某个连接通过系统调用正在读取数据,此时相对于其他事件来说,完全是阻塞状态,新连接无法处理、其他连接的 IO、查询 IO 读写以及事件分发都无法完成。

在多线程模型中,我们提到,其主要缺陷在于同一时间无法处理大量新连接、IO就绪事件;因此,将主从模式应用到这一块,就可以解决这个问题。
主从 Reactor 模式中,分为了主 Reactor 和 从 Reactor,分别处理 新建立的连接、IO读写事件/事件分发。
简言之,主从多线程模型由多个 Reactor 线程组成,每个 Reactor 线程都有独立的 Selector 对象。MainReactor 仅负责处理客户端连接的 Accept 事件,连接建立成功后将新创建的连接对象注册至 SubReactor。再由 SubReactor 分配线程池中的 I/O 线程与其连接绑定,它将负责连接生命周期内所有的 I/O 事件。
3 种模式可以用个比喻来理解:(餐厅常常雇佣前台接待员负责迎接顾客,当顾客入坐后,服务员专门为这张桌子服务,点好菜后,厨师负责做饭)

NIO 模型更适合需要大量在线活跃连接的场景,常见于服务端;BIO 模型则适合只需要支持少量连接的场景,数据库连接池使用的事这个模型。
reactor的代码实现:https://github.com/bruce256/NIO
参考资料