如何实现单服务器高性能

单服务器高性能

    • PPC(Process per Connection)
    • Prefork
    • TPC(Thread per Connection)
    • Prethread
    • Reactor

服务器采取的网格变成模型是实现单服务器高性能的关键之一,其两个设计点在于:
1、服务器如何管理连接;
2、服务器如何处理请求;

以下是服务器处理请求的几种模式

PPC(Process per Connection)

PPC是指每次有新的连接就建立一个新的进程去处理这个请求,这是传统的UNIX网络服务器采用的模型。
流程如下:
1)父进程接受连接
2)父进程将连接fork进子进程
3)子进程处理连接的读写请求
4)子进程关闭连接

注:当父进程fork进子进程后,子进程进行read、业务处理、write操作,一系列操作完成之后子进程会调用close进行主动关闭子进程,然后返回父进程,父进程也会调用close操作,但此close非彼close,这时父进程调用的close仅仅是将连接的文件描述符引用计数减一,并不会真真的去关闭父进程,只有当连接的文件描述符引用计数等于0是,操作系统才会真正关闭连接。

PPC模式设计比较简单,在服务器的连接数没有那么多的情况下比较适合,所以在互联网起初发展的时候,服务器的访问量和请求并发量还没有上去的时候,这种模式实际是运作的还是很好的。但互联网这几年的快速发展,访问量和并发量已突破成千上万,这种模式的弊端就被放大了:
1)fork代价高
2)父子进程通信复杂
3)进程数量增大后对操作系统的压力也十分大

因此针对以上的缺点,也产生了不同的解决方案。

Prefork

由于PPC模式,当每次父进程接受到连接后才会fork进新的子进程操作会对操作系统产生很大的消耗,因此为了解决实时fork而导致用户访问较慢的问题,出现了prefork模式。
其实prefork模式就是提前将子进程预先创建好,相比PPC模式,省去了实时fork这个操作,会让用户的访问更快、体验更好。
prefork的实现关键就是多个子进程都会接受同一个请求,所以当新的连接进入时,操作系统保证只能有一个进程能最后接受成功。但这样的模式下,存在着一个小小的问题,虽然只有一个子进程能接受处理成功,但是所有阻塞在accept上的子进程都会被唤醒,这样就导致了不必要的进程调度和上下文切换。
其实prefork模式仅仅只是省略了实时fork的操作,其余的问题还是和PPC模式一样,存在父子进程通信复杂、支持的并发连接数量有限的问题,因此实际应用也不多。Apache服务器提供了MPM prefork模式,默认情况的最大支持256个并发连接。

TPC(Thread per Connection)

TPC,指每次都有一个新的连接时就去新建一个线程,用于专门处理这个连接的请求。与进程相比,线程更轻量级,创建线程的消耗要比进程少得多,并且多线程是共享进程内存空间的,线程通信比进程通信更简单。因此从实际上将,TPC模式其实可以说是弱化了PPC的问题。
TPC的运行流程:
1)父进程接受连接
2)父进程创建子线程
3)子线程处理连接的读写请求
4)子线程关闭连接

注:和PPC相比,父进程不用进行关闭close进程的操作。因为子线程是共享父进程的进程空间的,连接的文件描述符并没有被复制,因此只需要一次close操作。

TPC虽然看似解决了PPC带来的问题,但是也引入了新的问题。
1、创建线程对系统性能的消耗相比进程是小得多,但并不是没有影响,在高并发的场景下,并发数达到每秒上万时还是会出现性能问题。
2、进程之间不需要通信,但是线程之间的互斥和共享又引入了新的复杂度,也许会因为某个问题从而导致线程的死锁。
3、多线程会出现相互影响的问题,某个线程出现异常很有可能会导致整个进程的退出,从而影响业务流程。
综上所述,从本质上看PPC和TPC在单服务器上的性能解决方案是基本类似的。但由于TPC在高并发的场景下会导致死锁问题。因此,在并发数几百的情况下,更多的采用的确实PPC方案,因为PPC方案不会有死锁的风险,也不会多进程互相影响,稳定性更高。

Prethread

类似于Prefork,为了解决TPC模式中当连接进来时才创建新的线程来处理连接请求的这种方式,设计出Prethread这种方式,虽然创建线程比创建进程要更加轻量级,但是还是有一定的代价,因此Prethread会预先创建线程。
由于多线程之间的数据共享和通信比较方便,因此实际上prethread的实现方式相比prefork要灵活一些,常见的实现方式有下面几种:
1)主进程accept,然后将连接交给某个线程处理
2)子线程都尝试去accept,最终只有一个线程accept成功
Apache服务器的MPM worker模式本质上就是一种prethread方案,但是稍微做了一些改进。Apache会创建多个进程,每个进程里再创建多个线程,这样做主要是考虑稳定性,即使某个子进程里面的某个线程异常导致整个子进程退出,还会有其他子进程继续提供服务,不会导致整个服务器全部挂掉。
prethread理论上可以比prefork支持更多的并发连接,Apache服务器MPM worker模式默认支持16*25个并发处理线程。

Reactor

为了解决PPC带来的最主要的问题(TPC类似的问题):每个连接都要创建进程,连接结束之后就销毁;一个自然的想法就是资源复用,即不再单独为每个连接创建进程,而是创建一个进程池,将连接分配给进程,一个进程可以处理多个连接的业务。
当引入资源池的连接处理方式后,又出现另一个新的问题:如何才能高效的处理多个链接的业务?当一个连接一个进程时,进程可以采用“read->业务处理->write”的处理流程,如果当前没有数据可读,则进程就阻塞在read操作上。就好比我们去图书馆借书,首先你需要一台电脑先查询图书馆有没有你需要的那一本书,如果没有你需要的那本书,你就占着那台电脑一直等,这种情况,在一个人就使用一台电脑的情况下时没有问题的,但是有多个人在排队使用你这台电脑时,如果你阻塞了,那么整个进程就会一直处于阻塞的状态,即使你后面的人需要的书是存在的,也无法去查询。所以如果服务器这样去处理多个连接的时候,是无法做到高性能的。
解决这样的问题其实很简单,将read操作改为非阻塞,然后进程不断的轮询多个连接。即当你想借的书没有时,你就将电脑让给其他同学,这样的方式能够解决阻塞问题,但是并不够优雅。首先轮询是要消耗CPU的,其次如果一个进程处理几千上万的连接,则轮询的效率是很低的。
因此为解决上述的问题,I/O多路复用技术就此诞生,只有当连接上有数据的时候进程才去处理。
I/O多路复用技术关键两点:
1)当多条连接共用一个阻塞对象之后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接。
2)当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理
I/O多路复用结合线程池,完美的解决了PPC和TPC模型的问题,被命名为:Reactor,中文为”反应堆“。
Reactor模式的核心组成部分包括Reactor和处理资源池(进程池或线程池),其中Reactor负责监听和分配事件,处理资源池负责处理事件。结合不同的业务场景,Reactor模式的具体实现方案灵活多变,主要体现如下:
1)Reactor的数量可以变化:可以时一个Reactor,也可以是多个Reactor
2)资源池的数量可以变化:以进程为例,可以是单个进程,也可以是多个进程(线程类似)
将以上两个因素排列组合一下,理论上可有四种选择,但由于”多Reactor单进程“实现方案比”单Reactor“方案,既复杂又没有性能优势,因此仅仅是一个理论上的方案,并没有实际应用。最终Reactor模式的三种典型实现方案如下:
1)单Reactor单进程/单线程
2)单Reactor多线程
3)多Reactor多进程/线程
以上方案具体选择进程还是线程,更多需要结合编程语言和平台等相关因素。

你可能感兴趣的:(服务器)