知识卡片

多Reactor多进程/线程:职责分离反而让实现更简单

结构图卡

内容

针对[[单Reactor单进程与单Reactor多线程的取舍]]里”Reactor本身仍是单点瓶颈”这个问题,最直接的解法就是把单Reactor也拆成多个,形成多Reactor多进程/线程方案:父进程里的mainReactor只负责用select监听”连接建立”这一类事件,通过Acceptor接收新连接后,把这个连接分配给某个子进程;子进程里的subReactor把分到的连接加入自己的监听队列,并创建对应的Handler,之后这个连接上发生的所有事件(数据可读、可写等)都由这个子进程自己的subReactor监听、自己的Handler走完read→业务处理→send的完整流程。这个方案表面上看比单Reactor多线程更复杂(多了一层Reactor),但实际实现起来反而更简单,原因有三层:父进程和子进程的职责被切得非常干净——父进程只管接新连接,子进程只管把接到的连接的后续业务处理完,不存在职责重叠;父子进程之间的交互也很简单——父进程只需要把新连接”甩”给子进程,子进程不需要再把处理结果传回父进程(不像单Reactor多线程里子线程处理完还要把结果传回主线程的Handler);子进程之间彼此独立,网络模型这一层(select、read、send等)完全不需要任何同步共享机制(虽然”业务处理”这一层本身仍可能需要同步共享,但和网络模型这层完全脱钩)。目前著名的开源实现里,Nginx用的是多Reactor多进程,Memcache和Netty用的是多Reactor多线程;不过Nginx的实际实现和标准的多Reactor多进程方案有一个关键差异——它的主进程只负责创建监听端口,并不创建单独的mainReactor去accept连接,accept这个动作实际上是由各个子进程自己的Reactor来做的,通过锁机制保证同一时刻只有一个子进程在执行accept,accept到新连接后这个连接就直接归属这个子进程自己的Reactor处理,不会再被分配给别的子进程——这个设计进一步简化了父子进程之间原本还需要”分配连接”这一步的交互。

结构图

flowchart TB
  A["多Reactor多进程/线程"]
  A --> B["mainReactor(父进程)<br/>只负责select监听连接建立事件+Acceptor接收"]
  B --> C["把新连接分配给某个子进程"]
  C --> D["subReactor(子进程)<br/>监听自己名下连接的后续所有事件"]
  D --> E["Handler完成read→业务处理→send"]
  A --> F["为何反而更简单:<br/>①父子职责清晰(父接连接/子处理业务)<br/>②交互简单(父只需甩连接,子无须回传结果)<br/>③子进程间网络模型层无须同步共享"]
  A --> G["Nginx的差异化实现:<br/>父进程只开监听端口,不设mainReactor<br/>由子进程Reactor自己accept(加锁保证同时只1个accept)<br/>accept到的连接归属自己处理,不再分配"]

参考来源

- 位置:《从零开始学架构》第19讲《单服务器高性能模式:Reactor与Proactor》"多Reactor多进程/线程"(源文件:_epub-src/OEBPS/text00001.html) - 结论依据:原文说明"多 Reactor 多进程 / 线程的方案看起来比单 Reactor 多线程要复杂,但实际实现时反而更加简单,主要原因是:父进程和子进程的职责非常明确……父进程和子进程的交互很简单……子进程之间是互相独立的,无须同步共享之类的处理",并说明"Nginx 采用的是多 Reactor 多进程的模式,但方案与标准的多 Reactor 多进程有差异……主进程中仅仅创建了监听端口,并没有创建 mainReactor 来'accept'连接,而是由子进程的 Reactor 来'accept'连接,通过锁来控制一次只有一个子进程进行'accept'",直接支撑本卡片结论与结构图。 - 原始内容:多 Reactor 多进程 / 线程的方案看起来比单 Reactor 多线程要复杂,但实际实现时反而更加简单……父进程和子进程的职责非常明确,父进程只负责接收新连接,子进程负责完成后续的业务处理……Nginx 采用的是多 Reactor 多进程的模式,但方案与标准的多 Reactor 多进程有差异。