You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

124 lines
9.8 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 18 | 单服务器高性能模式PPC与TPC
高性能是每个程序员的追求无论我们是做一个系统还是写一行代码都希望能够达到高性能的效果而高性能又是最复杂的一环磁盘、操作系统、CPU、内存、缓存、网络、编程语言、架构等每个都有可能影响系统达到高性能一行不恰当的debug日志就可能将服务器的性能从TPS 30000降低到8000一个tcp\_nodelay参数就可能将响应时间从2毫秒延长到40毫秒。因此要做到高性能计算是一件很复杂很有挑战的事情软件系统开发过程中的不同阶段都关系着高性能最终是否能够实现。
站在架构师的角度,当然需要特别关注高性能架构的设计。高性能架构设计主要集中在两方面:
* 尽量提升单服务器的性能,将单服务器的性能发挥到极致。
* 如果单服务器无法支撑性能,设计服务器集群方案。
除了以上两点,最终系统能否实现高性能,还和具体的实现及编码相关。但架构设计是高性能的基础,如果架构设计没有做到高性能,则后面的具体实现和编码能提升的空间是有限的。形象地说,架构设计决定了系统性能的上限,实现细节决定了系统性能的下限。
单服务器高性能的关键之一就是**服务器采取的并发模型**,并发模型有如下两个关键设计点:
* 服务器如何管理连接。
* 服务器如何处理请求。
以上两个设计点最终都和操作系统的I/O模型及进程模型相关。
* I/O模型阻塞、非阻塞、同步、异步。
* 进程模型:单进程、多进程、多线程。
在下面详细介绍并发模型时会用到上面这些基础的知识点所以我建议你先检测一下对这些基础知识的掌握情况更多内容你可以参考《UNIX网络编程》三卷本。今天我们先来看看单服务器高性能模式PPC与TPC。
## PPC
PPC是Process Per Connection的缩写其含义是指每次有新的连接就新建一个进程去专门处理这个连接的请求这是传统的UNIX网络服务器所采用的模型。基本的流程图是
![](https://static001.geekbang.org/resource/image/53/ba/53b17d63a31c6b551d3a039a2568daba.jpg)
* 父进程接受连接图中accept
* 父进程“fork”子进程图中fork
* 子进程处理连接的读写请求图中子进程read、业务处理、write
* 子进程关闭连接图中子进程中的close
注意图中有一个小细节父进程“fork”子进程后直接调用了close看起来好像是关闭了连接其实只是将连接的文件描述符引用计数减一真正的关闭连接是等子进程也调用close后连接对应的文件描述符引用计数变为0后操作系统才会真正关闭连接更多细节请参考《UNIX网络编程卷一》。
PPC模式实现简单比较适合服务器的连接数没那么多的情况例如数据库服务器。对于普通的业务服务器在互联网兴起之前由于服务器的访问量和并发量并没有那么大这种模式其实运作得也挺好世界上第一个web服务器CERN httpd就采用了这种模式具体你可以参考[https://en.wikipedia.org/wiki/CERN\_httpd](https://en.wikipedia.org/wiki/CERN_httpd))。互联网兴起后,服务器的并发和访问量从几十剧增到成千上万,这种模式的弊端就凸显出来了,主要体现在这几个方面:
* fork代价高站在操作系统的角度创建一个进程的代价是很高的需要分配很多内核资源需要将内存映像从父进程复制到子进程。即使现在的操作系统在复制内存映像时用到了Copy on Write写时复制技术总体来说创建进程的代价还是很大的。
* 父子进程通信复杂父进程“fork”子进程时文件描述符可以通过内存映像复制从父进程传到子进程但“fork”完成后父子进程通信就比较麻烦了需要采用IPCInterprocess Communication之类的进程通信方案。例如子进程需要在close之前告诉父进程自己处理了多少个请求以支撑父进程进行全局的统计那么子进程和父进程必须采用IPC方案来传递信息。
* 支持的并发连接数量有限如果每个连接存活时间比较长而且新的连接又源源不断的进来则进程数量会越来越多操作系统进程调度和切换的频率也越来越高系统的压力也会越来越大。因此一般情况下PPC方案能处理的并发连接数量最大也就几百。
## prefork
PPC模式中当连接进来时才fork新进程来处理连接请求由于fork进程代价高用户访问时可能感觉比较慢prefork模式的出现就是为了解决这个问题。
顾名思义prefork就是提前创建进程pre-fork。系统在启动的时候就预先创建好进程然后才开始接受用户的请求当有新的连接进来的时候就可以省去fork进程的操作让用户访问更快、体验更好。prefork的基本示意图是
![](https://static001.geekbang.org/resource/image/3c/2f/3c931b04d3372ebcebe4f2c2cf59d42f.jpg)
prefork的实现关键就是多个子进程都accept同一个socket当有新的连接进入时操作系统保证只有一个进程能最后accept成功。但这里也存在一个小小的问题“惊群”现象就是指虽然只有一个子进程能accept成功但所有阻塞在accept上的子进程都会被唤醒这样就导致了不必要的进程调度和上下文切换了。幸运的是操作系统可以解决这个问题例如Linux 2.6版本后内核已经解决了accept惊群问题。
prefork模式和PPC一样还是存在父子进程通信复杂、支持的并发连接数量有限的问题因此目前实际应用也不多。Apache服务器提供了MPM prefork模式推荐在需要可靠性或者与旧软件兼容的站点时采用这种模式默认情况下最大支持256个并发连接。
## TPC
TPC是Thread Per Connection的缩写其含义是指每次有新的连接就新建一个线程去专门处理这个连接的请求。与进程相比线程更轻量级创建线程的消耗比进程要少得多同时多线程是共享进程内存空间的线程通信相比进程通信更简单。因此TPC实际上是解决或者弱化了PPC fork代价高的问题和父子进程通信复杂的问题。
TPC的基本流程是
![](https://static001.geekbang.org/resource/image/25/e7/25b3910c8c5fb0055e184c5c186eece7.jpg)
* 父进程接受连接图中accept
* 父进程创建子线程图中pthread
* 子线程处理连接的读写请求图中子线程read、业务处理、write
* 子线程关闭连接图中子线程中的close
注意和PPC相比主进程不用“close”连接了。原因是在于子线程是共享主进程的进程空间的连接的文件描述符并没有被复制因此只需要一次close即可。
TPC虽然解决了fork代价高和进程通信复杂的问题但是也引入了新的问题具体表现在
* 创建线程虽然比创建进程代价低,但并不是没有代价,高并发时(例如每秒上万连接)还是有性能问题。
* 无须进程间通信,但是线程间的互斥和共享又引入了复杂度,可能一不小心就导致了死锁问题。
* 多线程会出现互相影响的情况,某个线程出现异常时,可能导致整个进程退出(例如内存越界)。
除了引入了新的问题TPC还是存在CPU线程调度和切换代价的问题。因此TPC方案本质上和PPC方案基本类似在并发几百连接的场景下反而更多地是采用PPC的方案因为PPC方案不会有死锁的风险也不会多进程互相影响稳定性更高。
## prethread
TPC模式中当连接进来时才创建新的线程来处理连接请求虽然创建线程比创建进程要更加轻量级但还是有一定的代价而prethread模式就是为了解决这个问题。
和prefork类似prethread模式会预先创建线程然后才开始接受用户的请求当有新的连接进来的时候就可以省去创建线程的操作让用户感觉更快、体验更好。
由于多线程之间数据共享和通信比较方便因此实际上prethread的实现方式相比prefork要灵活一些常见的实现方式有下面几种
* 主进程accept然后将连接交给某个线程处理。
* 子线程都尝试去accept最终只有一个线程accept成功方案的基本示意图如下
![](https://static001.geekbang.org/resource/image/11/4d/115308f686fe0bb1c93ec4b1728eda4d.jpg)
Apache服务器的MPM worker模式本质上就是一种prethread方案但稍微做了改进。Apache服务器会首先创建多个进程每个进程里面再创建多个线程这样做主要是为了考虑稳定性即使某个子进程里面的某个线程异常导致整个子进程退出还会有其他子进程继续提供服务不会导致整个服务器全部挂掉。
prethread理论上可以比prefork支持更多的并发连接Apache服务器MPM worker模式默认支持16 × 25 = 400 个并发处理线程。
## 小结
今天我为你讲了传统的单服务器高性能模式PPC与TPC希望对你有所帮助。
这就是今天的全部内容,留一道思考题给你吧,什么样的系统比较适合本期所讲的高性能模式?原因是什么?
欢迎你把答案写到留言区,和我一起讨论。相信经过深度思考的回答,也会让你对知识的理解更加深刻。(编辑乱入:精彩的留言有机会获得丰厚福利哦!)