C/C++教程

单服务器高性能模式:PPC与TPC

本文主要是介绍单服务器高性能模式:PPC与TPC,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

极客时间:《从 0 开始学架构》:单服务器高性能模式:PPC与TPC

1、引言

高性能是系统中最复杂的一环,其中磁盘、操作系统、CPU、内存、缓存、网络、编程语言、架构等,每个都有可能影响系统达到高性能,因此,要做到高性能计算是一件很复杂很有挑战的事情,软件系统开发过程中的不同阶段都关系着高性能最终是否能够实现。
高性能架构设计主要集中在两方面:

  • 尽量提升单服务器的性能,将单服务器的性能发挥到极致。
  • 如果单服务器无法支撑性能,设计服务器集群方案。

除了以上两点,最终系统能否实现高性能,还和具体的实现及编码相关。架构设计决定了系统性能的上限,实现细节决定了系统性能的下限。
单服务器高性能的关键之一就是服务器采取的并发模型,并发模型有如下两个关键设计点:

  • 服务器如何管理连接。
  • 服务器如何处理请求。

以上两个设计点最终都和操作系统的 I/O 模型及进程模型相关。

  • I/O 模型:阻塞、非阻塞、同步、异步。
  • 进程模型:单进程、多进程、多线程。

本节讲下单服务器高性能两种模式:PPC 与 TPC

2、PPC

PPC 是 Process Per Connection 的缩写,其含义是指每次有新的连接就新建一个进程去专门处理这个连接的请求,这是传统的 UNIX 网络服务器所采用的模型。基本的流程图是:

  • 父进程接受连接(图中 accept)。
  • 父进程“fork”子进程(图中 fork)。
  • 子进程处理连接的读写请求(图中子进程 read、业务处理、write)。
  • 子进程关闭连接(图中子进程中的 close)。

但互联网兴起后,服务器的并发和访问量从几十剧增到成千上万,这种模式的弊端就凸显出来了,主要体现在这几个方面:

  • fork 代价高:站在操作系统的角度,创建一个进程的代价是很高的,需要分配很多内核资源,需要将内存映像从父进程复制到子进程。即使现在的操作系统在复制内存映像时用到了 Copy on Write(写时复制)技术,总体来说创建进程的代价还是很大的。
  • 父子进程通信复杂:父进程“fork”子进程时,文件描述符可以通过内存映像复制从父进程传到子进程,但“fork”完成后,父子进程通信就比较麻烦了,需要采用 IPC(Interprocess Communication)之类的进程通信方案。例如,子进程需要在 close 之前告诉父进程自己处理了多少个请求以支撑父进程进行全局的统计,那么子进程和父进程必须采用 IPC 方案来传递信息。
  • 支持的并发连接数量有限:如果每个连接存活时间比较长,而且新的连接又源源不断的进来,则进程数量会越来越多,操作系统进程调度和切换的频率也越来越高,系统的压力也会越来越大。因此,一般情况下,PPC 方案能处理的并发连接数量最大也就几百。

prefork

顾名思义,prefork 就是提前创建进程(pre-fork)。系统在启动的时候就预先创建好进程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去 fork 进程的操作,让用户访问更快、体验更好。prefork 的基本示意图是:

prefork 的实现关键就是多个子进程都 accept 同一个 socket,当有新的连接进入时,操作系统保证只有一个进程能最后 accept 成功。
但会出现“惊群”现象(虽然只有一个子进程能 accept 成功,但所有阻塞在 accept 上的子进程都会被唤醒,这样就导致了不必要的进程调度和上下文切换了),Linux 2.6 版本后内核已经解决了 accept 惊群问题。

3、TPC

TPC 是 Thread Per Connection 的缩写,其含义是指每次有新的连接就新建一个线程去专门处理这个连接的请求。与进程相比,线程更轻量级,创建线程的消耗比进程要少得多;同时多线程是共享进程内存空间的,线程通信相比进程通信更简单。因此,TPC 实际上是解决或者弱化了 PPC fork 代价高的问题和父子进程通信复杂的问题。
TPC的流程:

  • 父进程接受连接(图中 accept)。
  • 父进程创建子线程(图中 pthread)。
  • 子线程处理连接的读写请求(图中子线程 read、业务处理、write)。
  • 子线程关闭连接(图中子线程中的 close)。

注意,和 PPC 相比,主进程不用“close”连接了。原因是在于子线程是共享主进程的进程空间的,连接的文件描述符并没有被复制,因此只需要一次 close 即可。

prethread

和 prefork 类似,prethread 模式会预先创建线程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去创建线程的操作,让用户感觉更快、体验更好。
常见的实现方式有下面几种:

  • 主进程 accept,然后将连接交给某个线程处理。
  • 子线程都尝试去 accept,最终只有一个线程 accept 成功,方案的基本示意图如下:
    Apache 服务器的 MPM worker 模式本质上就是一种 prethread 方案,但稍微做了改进。Apache 服务器会首先创建多个进程,每个进程里面再创建多个线程,这样做主要是为了考虑稳定性,即:即使某个子进程里面的某个线程异常导致整个子进程退出,还会有其他子进程继续提供服务,不会导致整个服务器全部挂掉。

PS
不同并发模式的选择,还要考察三个指标,分别是响应时间(RT),并发数(Concurrency),吞吐量(TPS)。三者关系,吞吐量=并发数/平均响应时间。不同类型的系统,对这三个指标的要求不一样。
       三高系统,比如秒杀、即时通信,不能使用上述高性能模式
       三低系统,比如ToB系统,运营类、管理类系统,一般可以使用。
       高吞吐系统,如果是内存计算为主的,一般可以使用,如果是网络IO为主的,一般不能使用。

这篇关于单服务器高性能模式:PPC与TPC的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!