内容纲要
架构设计是高性能的基础。架构设计决定了性能的上限,实现细节决定性能的下限。
提升性能的方式
提升单台服务器性能
单服务器的高性能关键之一就是服务采取的并发模型。并发模型的关键设计点:
- 服务器如何管理连接
- 服务器如何处理请求
以上两点都和操作系统的 I/O 模型(阻塞、非阻塞、同步、异步)和进程模型(单进程、多进程、多线程)有关。
单服务器高性能模式
PPC(Process Per Conenction)
即每个新连接进来,就创建一个进程去专门处理这个连接。
- 父进程接收(accept)连接
- 父进程(fork)子进程
- 子进程读(read)写(write)数据和处理业务
- 子进程关闭(close)连接
父进程的 close 是让连接的文件描述符引用计数减一,并非关闭连接;真正的关闭连接由子进程完成。
PPC 模式的优点
- 实现简单
PPC 模式的缺点
- fork 代价高
- prefork(流程见下图):即提前 fork 进程,子进程共同 accept 一个 socket,可以减少连接过程中的进程创建时间
- 子进程共同监听一个 socket,容易导致惊群现象出现
- prefork(流程见下图):即提前 fork 进程,子进程共同 accept 一个 socket,可以减少连接过程中的进程创建时间
- 父子进程通信复杂
- 支持并发连接数有限(进程过多,系统负担太重)
TPC(Thread Per Connection)
即每个新连接进来,就创建一个线程专门去处理这个连接。
- 父进程接收(accept)连接
- 父进程创建子线程(pthread)
- 子线程读(read)写(write)数据和处理业务
- 子线程关闭(close)连接
TPC 的优点
- 创建线程资源消耗小(相比进程;消耗小,但依然是有消耗的)
- prethread:与 prefork 类似,但不存在“惊群现象”
- 线程间通信简单
- 主进程不需要 close socket 操作
TPC 的缺点
- 线程间互斥、共享等问题较复杂,设计不当容易出现死锁等问题
- 线程间可能互相影响(异常未处理好,可能导致整体崩溃)
Reactor
即每个连接进来,通过 I/O 多路复用技术,把连接交给相应的业务代码进行处理(即 I/O 多路复用统一监听事件,收到事件后分配(Dispatch)给某个进程)。Reactor 模式也叫 Dispatcher 模式。
核心组成:
- Reactor:负责监听、分配事件
- 处理资源池(进程池、线程池):负责处理事件
典型方案:
- 单 Reactor;单进程/线程
- 过程
- Reactor 对象通过 select 监控连接事件
- 收到事件后通过 Dispatch 分发
- 如果是建立连接事件,由 Acceptor 通过 accept 接受连接,并创建 Handle 处理后续业务
- 如果不是建立连接事件,Reactor 调用之前创建的对应 Handle 处理相关事件
- Handle 完成 read -> 业务处理 -> send 业务流程
- 优点
- 简单
- 无进程间通信、竞争
- 缺点
- 无法发挥多核性能(多部署增加系统复杂度)
- Handle 在除了业务时,进程无法处理其他连接事件
- 适用场景:业务处理非常快的场景(如:Redis)
- 过程
- 单 Reactor;多线程
- 过程
- 主线程 Reactor 通过 select 监控事件
- 收到事件后通过 Dispatch 分发
- 如果是建立连接事件,由 Acceptor 通过 accept 接收连接,并建立一个 Handle 处理后续业务
- 如果不是建立连接事件,则调用之前创建的对应 Handle 来处理后续业务
- Handle 负责响应事件,不处理具体业务;通过 read 读取到数据后发给 Processor 处理
- Processor 在独立子线程中处理业务;处理完后将响应结果发给主 Handle 处理
- 主 Handle 通过 send 将结果返回给 client
- 优点
- 可以发挥多核性能
- 缺点
- 多线程数据共享、访问复杂
- Reactor 承担所有事件监听和响应,压力大
- 过程
- 单 Reactor;多进程/线程
- 过程
- 父进程中 mainReactor 通过 select 监控连接建立事件
- 收到后通过 Acceptor 将新的连接分配给某个子进程
- 子进程通过 subReactor 将 mainReactor 分配的连接加入监听队列,并创建 Handle 用于处理各种事件
- 当有事件发生时,subReactor 调用之前创建的对应 Handle 进行处理
- Handle 完成 read->业务处理->send 的业务流程
- 优点
- 简单
- 父子进程分工明确:父进程监听连接,子进程完成后续业务
- 父子进程交互简单:只有父进程将连接交给子进程,子进程不需要跟父进程交互
- 子进程之间互不干扰(除非业务有交集)
- 简单
- 过程
Proactor(稍微了解就可以,应用环境不是很好)
Reactor 是非阻塞同步网络模型(read 和 send 都需要用户进程同步操作);把 read 和 send(I/O)改为异步,即 Proactor。
方案:
- 过程
- Proactor Initiator 负责创建 Proactor 和 Handle,并通过 Asynchronous Operation Processor(以下简称 AOP) 将 Proactor 和 Handle 都注册到内核
- AOP 负责处理注册请求,并完成 I/O 操作
- 完成操作后通知 Proactor
- Proactor 根据不同事件类型回调不同的 Handle 进行业务处理
- Handle 完成业务处理,也可以注册新的 Handle 到内核
Proactor 理论上效率比 Reactor 高,但要实现真正异步 I/O 操作系统需要完成大量工作。目前 Windows 通过 IOCP 实现真正异步 I/O;Linux 的 AIO 尚不完善,因此 Linux 下,基本还是使用 Reactor 为主。
并发模式的选择
不同并发模式的选择需要考虑的因素有:
- 响应时间(RT)
- 并发数(Concurrency)
- 吞吐量(TPS)
吞吐量 = 并发数 / 平均响应时间
不同的系统对不同指标的需求也不一样。