多进程模型(PPC)与多线程模型(TPC)的性能优化 | 关于服务器高性能优化的思考


基础知识

线程与进程

  • 概念
    线程:程序执行流的最小单元,系统独立调度和分配CPU(独立运行)的基本单位。
    进程:资源分配的基本单位,至少包含一个线程。
  • 区别
    线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程资源。
    每个进程都有自己一套独立的资源(数据),供其内部的所有线程共享。
    不论大小,开销线程都要更“轻量级”。
    同一个进程内的线程通信,要比进程间的通信更快速、高效。

单服务器高性能

单服务器高性能的关键之一,就是服务器采取的并发模型,并发模型有如下两个设计关键点:

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

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

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

PPC模型

PPC 是 Process Per Connection 的缩写,又称做Apache模型,是通过多进程来实现业务并发。具体操作就是每次有新的连接,就去创建一个进程来处理这个连接的请求,这也是传统的UNIX服务器所采取的模型。
基本的流程图是:
PPC模型

  1. 父进程接受连接(图中 accept)
  2. 父进程‘fork’子进程
  3. 子进程处理连接的读写请求(图中子进程 read、业务处理、write)
  4. 子进程关闭连接(图中的子进程close)

PPC 模型实现简单,但是每次连接都需要创建进程,开销较大,适合服务器的连接数没那么多的情况。
当服务器的并发量较大时,PPC模型的弊端主要体现在如下几个方面:

  1. fork代价高:站在操作系统的角度,创建一个进程的成本是很高的,需要单独分配内核资源,同时需要将内存映像从父进程复制到子进程。
  2. 父子进程间通信复杂:父进程‘fork’子进程时,文件描述符可以通过内存映像复制从父进程传到子进程,但fork完成后,父子进程间通信就比较麻烦了,需要采用IPC(Interprocess Communication)之类的进程通信方案。
  3. 支持的并发连接数量有限:如果每个连接存活的时间比较长,而新的连接又源源不断,则同时开启的进程会越来越多,操作系统进程调度和切换的频率会越来越高,系统的压力会越来越大。所以PPC模型一般可以处理的最大并发连接数也就几百左右。

prefork模式

在PPC模型中,当连接进来时才去fork子进程来处理连接请求,由于fork时成本比较高,速度比较慢,所以用户访问时可能会比较卡顿,prefork模式的出现,就是为了解决这个问题。
prefork 模式是指在系统启动时就预先创建好进程,然后再接收用户请求,当有新的连接进来时,省去fork过程,提升用户访问速度。
prefork模式基本示意图:
prefork模式
prefork模式的实现关键是多个进程都 accept 同一个 socket ,当有新的连接进入时,操作系统保证只有一个进程最后 accept 成功。
prefork 模式和 PPC 模式一样,存在父子进程间通信复杂、支持的并发数量少等问题,因此目前的实际应用并不多。Apache服务器提供了MPM prefork模式,可以在需要可靠性或者旧软件兼容的站点上使用这种模式,默认最大支持256个并发连接。

TPC模型

TPC 是 Thread Per Connection 的缩写,是一种多线程并发处理模型。具体操作就是每次有新的连接就新建一个线程去专门处理这个连接的请求。与进程相比,线程要更轻量级,创建线程消耗的资源要少的多,而且线程间共用进程的存储空间,通信比较简单。因此,TPC实际上是弱化了PPC fork 代价高的问题和父子进程间通信复杂的问题。
TPC的基本操作流程:
TPC模型

  1. 父进程接收连接(图中的accept)
  2. 父进程创建子线程(图中的pthread)
  3. 子线程处理请求(图中子线程read、业务处理、write)
  4. 子线程关闭连接(图中子线程中的close)

注意,和PPC相比,TPC中的主进程不用再close连接了。原因是子线程是共享主进程的进程空间的,连接的文件描述符并没有被复制,因此只需要close一次就可以了。
TPC虽然解决了 fork 代价高和子进程间通信复杂的问题,但也引入了新的问题,具体表现在:

  1. 创建线程虽然比进程代价低,但并不是没有代价,在高并发时(例如每秒上万连接),还是有性能问题。
  2. 无需进程间通信,但是线程间的互斥和共享又引入了复杂度,可能一不小心又导致了死锁问题。
  3. 多线程会出现互相影响的情况,某个线程出现异常时,可能会导致整个进程退出(例如内存越界)。

除了引入了新的问题,TPC还是存在CPU线程调度和切换代价的问题。因此,TPC方案和PPC方案本质上基本类似,在并发几百连接的场景下,反而更多的采用PPC方式,因为PPC方案不会有死锁的风险,也不会多进程相互影响,稳定性更高。

prethread模式

TPC模型中,当连接进来时才创建线程来处理连接请求,虽然创建线程比创建进程更轻量级,但还是有一定的代价, prethread 模式就是为了解决这一问题。
和prefork模式类似,prethread模式会预先创建线程,然后再接收用户请求,当有新的用户请求进来时,就可以省掉创建线程的操作,响应速度更快、用户体验更好。
常见的prethread实现方式:

  1. 主进程 accept,然后将连接交给某个线程处理
  2. 子线程都尝试去 accept ,最后只有一个线程可以 accept 成功。

示意图:
prethread模式
Apache服务器的MPM worker模式本质上就是一种prethread方案,但稍微做了改进。Apache服务器会首先创建多个进程,每个进程里面再创建多个线程,这么做主要是为了保证稳定性,即使某个子进程里面的某个线程异常导致整个子进程退出,还会有其他子进程继续提供服务,不会导致整个服务器全部挂掉。
prethread 理论上可以比 prefork 支持更多的并发连接,Apache 服务器 MPM worker 模式默认支持 16 × 25 = 400 个并发处理线程。

参考链接

本文发表于2018年09月27日 11:23
阅读 111 讨论 1 喜欢 2

讨论

周娱

君子和而不同
按照自己的方式,去度过人生

4601 1243872
抢先体验

扫码体验
趣味小程序
文字表情生成器

加入组织

扫码添加周娱微信
备注“加入组织”
邀请进开发群

闪念胶囊

不积跬步无以至千里,越焦虑越要扎实干。

不要试图鹤立鸡群,趁早离开那群鸡!

程序员过节需要的不是美女、不是美食、不是不加班!他们需要的是写代码,一群人写、往死里写、通宵写!!那种暗流涌动的狂欢,远比虚无庸俗的食色更让他们振奋!! by芋头

面试的时候,常常会问数组和链表的区别,很多人都回答说,“链表适合插入、删除,时间复杂度 O(1);数组适合查找,查找时间复杂度为 O(1)”。 实际上,这种表述是不准确的。数组是适合查找操作,但是查找的时间复杂度并不为 O(1)。即便是排好序的数组,你用二分查找,时间复杂度也是 O(logn)。 所以,正确的表述应该是,数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)。

找一个bug就好比从一泡烂泥里找一条泥鳅,写一个bug就好比往一泡烂泥里丢一条泥鳅进去

数据结构在某种程度上和设计模式类似,都是前辈的武功套路。不同的是,设计模式是近几十年的卓越程序员的智慧结晶,而数据结构是几百上千年的无数科学家、数学家的智慧沉淀,更加具有深厚的背景。

18年元旦立下的flag要集中突击一下了.....

人生是一场马拉松,起跑的优劣对于整段路途而言并没有那么重要,笑到最后的都是一直在跑的人,也就是一辈子都在学习的人。

角色是谁并不重要,重要的是会不会抢戏~

Copyright © 2016 - 2018 Cion.
All Rights Reserved.
备案:鲁ICP备16007319号.