彻底搞懂高性能网络模式Reactor 和 Proactor

服务端处理网络请求流程图

38531152b2ec84ddd21df2d3990bd3db.png

可以看到,主要处理步骤包括:

  • 获取请求数据, 客户端与服务器建立连接发出请求,服务器接受请求(1-3)
  • 构建响应, 当服务器接收完请求,并在用户空间处理客户端的请求「read -> 业务处理 -> send」,直到构建响应完成(4)
  • 返回数据, 服务器将已构建好的响应再通过内核空间的网络I/O发还给客户端(5-7)

设计服务端并发模型时,主要有如下两个关键点:

  • 服务器如何管理连接,获取输入数据
  • 服务器如何处理请求

以上两个关键点最终都与操作系统的I/O模型以及线程(进程)模型相关,下面详细介绍这两个模型

演进

服务端的架构演进主要围绕提高单位时间内请求数上. 其核心思想是资源复用.

1.如果要让服务器服务多个客户端,那么最直接的方式就是为每一条连接创建线程。(阻塞式IO,多线程/进程)

2.资源复用: 线程池(阻塞式IO,多线程/进程)
上图中, 多线程中无请求时read操作默认会阻塞当前线程, 当前线程就没办法继续处理其他连接的业务

3.解决线程阻塞问题, 最简单的方式就是将 socket 改成非阻塞,然后线程不断地轮询调用 read 操作来判断是否有数据(非阻塞式IO,多线程/进程)
这种方式虽然该能够解决阻塞的问题,但是解决的方式比较粗暴,因为轮询是要消耗 CPU 的,而且随着一个 线程处理的连接越多,轮询的效率就会越低。

4.I/O多路复用, 一个线程可以通过一个系统调用函数从内核中获取多个事件来管理多个IO流. 这样一个线程就可以管理多个请求了.(IO多路复用,多线程/进程, 又叫Reactor模式)

线程可以通过一个系统调用函数从内核中获取多个事件, 在获取事件时,先把我们要关心的连接传给内核,再由内核检测:

如果没有事件发生,线程只需阻塞在这个系统调用,而无需像前面的线程池方案那样轮训调用read操作来判断是否有数据。
如果有事件发生,内核会返回产生了事件的连接,线程就会从阻塞状态返回,然后在用户态中再处理这些连接对应的业务即可。

注意: io多路复用, 在第二阶段从内核空间中复制数据到用户空间是阻塞的, 复制数据的快慢影响程序的快慢.

5.异步网络模式(异步IO,多线程/进程, 又叫Proactor模式)
真正的异步IO, 参考第5种io模型, 可惜的是,在 Linux 下的异步 I/O 是不完善的, 所以现在主流的io模型还是io多路复用.

现在 Linux 下的异步 I/O 是不完善的,aio 系列函数是由 POSIX 定义的异步操作接口,不是真正的操作系统级别支持的,而是在用户空间模拟出来的异步,并且仅仅支持基于本地文件的 aio 异步操作,网络编程中的 socket 是不支持的,这也使得基于 Linux 的高性能网络程序都是使用 Reactor 方案。

IO模型

I/O主要为:网络IO(本质是socket文件读取)、磁盘IO

每次IO,都要经由两个阶段:

  • 第一步:将数据从文件先加载至内核内存空间(缓冲区),等待数据准备完成,时间较长
  • 第二步:将数据从内核缓冲区复制到用户空间的进程的内存中,时间较短

(具体的io模型见之前文章)

线程(进程)模型

(见之前文章)

Reactor 模式

I/O复用结合线程/进程,这就是Reactor模式基本设计思想.

单Reactor单进程/线程

单 Reactor 单进程的方案因为全部工作都在同一个进程内完成,所以实现起来比较简单,不需要考虑进程间通信,也不用担心多进程竞争。

但是,这种方案存在 2 个缺点:

  • 第一个缺点,因为只有一个进程,无法充分利用 多核 CPU 的性能;
  • 第二个缺点,Handler 对象在业务处理时,整个进程是无法处理其他连接的事件的,如果业务处理耗时比较长,那么就造成响应的延迟;

单Reactor多线程/进程

单 Reator 多线程的方案优势在于能够充分利用多核 CPU 的能,那既然引入多线程,那么自然就带来了多线程竞争资源的问题。
另外,「单 Reactor」的模式还有个问题,因为一个 Reactor 对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方。

多Reactor多进程/线程

要解决「单 Reactor」的问题,就是将「单 Reactor」实现成「多 Reactor」,这样就产生了第 多 Reactor 多进程 / 线程的方案。

Proactor 模式

与Reactor不同,Proactor模式将所有的I/O操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑

Reactor 与 Proactor 区别

  • Reactor 是在事件发生时就通知事先注册的事件(读写在应用程序线程中处理完成);
  • Proactor 是在事件发生时基于异步 I/O 完成读写操作(由内核完成),待 I/O 操作完成后才回调应用程序的处理器来进行业务处理。

扩展阅读

高性能网络框架:Reactor 和 Proactor
如何理解高性能网络模型?这篇文章说透了
C#服务端框架设计与实现
IO和零拷贝
9.3 高性能网络模式:Reactor 和 Proactor

此处评论已关闭