这段时间对Nginx+PHP-FPM的概念和机制一直不太清晰,趁着同事的分享和看过的几篇博文和资料,重新将思路处理一下。
首先,PHP-FPM(FastCGI Process Manager: FastCGI进程管理器)是一种针对PHP的FastCGI,针对PHP语言的一种进程管理器。FastCGI顾名思义就是一种快速的CGI(Common Gateway Interface)。因此为了能够理解PHP-FPM,我们首先应该了解CGI是怎么样一个机制。
1 CGI(Common Gateway Interface)
CGI(通用网关接口)是架起服务器Web Server和处理来自客户端请求的程序之间沟通的桥梁,也就是说是一个第三方的协议规定。当服务器接收到用户端的请求是静态请求时,Web Server会去对应的文件系统中找到这个文件,然后发送给浏览器。但是如果用户的请求是一种动态的请求,Web Server (如Nginx)就不知道如何处理这个请求了,既然自己不知道如何处理,就要求助或者说是承包给第三方。但是交给第三方处理这个请求的时候,总得告诉第三方一些请求的参数吧,这就像软件领域的需求的对接一样,规定我给你什么输入,然后你给我什么输出结果,CGI就是这种约定,规定了输入的参数和出输出的结果的一种协议。在处理动态请求的情况下,其流程图如下:
图1 CGI对请求处理流程图
用文字描述如下:
1.用户通过Http将动态请求发送到Web Server;
2.Web Server通过使用unix domain socket,或者ip socket将请求转发给CGI(如果使用ip socket的话,允许将Web Server 和CGI部署在不同的机器上);
3.CGI fork一个对应的进程处理来自客户端的动态请求,得到执行的结果;
4.执行的结果经过同一个socket连接通过CGI返回给Web server;
5.有Web Server将数据发送个客户端;
也就是说在这个过程中Web Server只是负责执行静态页面的请求以及接受用户的请求动态请求并转发给相应的处理程序,然后将动态请求处理程序的结果返回给客户端。这可以减轻Web Server的工作负担,使得Web Serve能够在更高并发的请求环境中工作。
2 FastCGI的工作方式
CGI虽然提供了Web Server和请求处理程序之间的一种接口规范,但是却饱受诟病。如上图所示,其原因在于CGI是以一种按需派生的方式工作的,也就是说在接收到Web Server转发的请求之后,就需要重新fork一个进程处理来自客户端的请求,但是fork一个进程的代价一般比较昂贵。在并发性不是很高的情况下,这种工作方式也许能工作的很好,但是如果并发性很高就会导致频繁的fork进程并处理请求,给运行CGI的机器造成很大的压力。其实仔细分析fork进程(如图3所示)所发生的事情可以清晰地看到有一些步骤是重复进行的,因此这些步骤是可以省略掉的,因此就诞生了FastCGI。FastCGI是在CGI的架构思想的扩展,其核心思想在于在Web Server和CGI之间建立起一个智能的可持续的中间层用于统筹CGI程序的管理,因此FastCGI可以被看做是一个CGI的进程管理器。其工作流程图如下:
图2 FastCGI对请求的处理流程图
其中的Fastcig-wrapper可以理解为用于启动另一个程序的程序。FastCGI的工作流程如下所述:
1.FastCGI进程管理器执行自身的初始化,启动多个worker进程并等待来自Web Server的连接,其中启动FastCGI进程的时候可以配置以ip socket和unix domain socket两种方式启动;
2.当客户端请求到达Web Server的时候,Web Server将请求采用ip socket或者unix domain socket发送到FastCGI的主进程(也叫master进程),master进程选择连接到一个worker进程。Web服务器将CGI的环境变量和标准输入发送到worker进程;
3.worker进程处理完成之后将输出通过同一个Socekt连接发送会Web Server。当worker进程处理完请求之后,关闭socket连接。如果没有达到该进程处理的请求数为其设置的上限,则继续存在而不会消亡,等待下次的处理请求。
3 CGI和FastCGI的不同之处
为了清晰地说明CGI和FastCGI之间的不同,图3说明了FastCGI和CGI之间的执行步骤的不同之处。图中Individual Request代表着FastCGI的请求处理流程图,而PHP Lifespan很明显就代表着CGI处理请求的流程图。从图中可以看出相对于CGI而言FastCGI处理外部的动态请求时不用为每个请求执行PHP的MINIT(即PHP的模块初始化过程,需要加载php.ini,为php启动的第一个步骤)过程。之所以会出现这种情况,究其原因在于worker进程启动的时候这个步骤已经完成了,worker进程是常驻内存的且不止可以处理一次请求,因此只需要针对特定的请求初始化(RINIT)就可以了,也就是说不用对每次请求都加载一次php.ini。在处理完本次请求之后,如果没有到达处理请求的上限该进程也不用消亡,也就没有了后面的扩展关闭步骤。但是CGI的执行模式是为每个请求fork一个子进程,每个子进程都是重新加载配置文件,按照php执行的步骤按部就班的执行,这也就造成了直接使用CGI的代价昂贵,尤其是在高并发的情况下。
图2 CGI和FastCGI声明周期示意图
4. PHP FastCGI---PHP-FPM的架构实现
PHP-FPM的进程管理方式为:存在的master进程可以管理多个pool,每个pool由master进程监听不同的端口,每个pool中又可以存在许多不同的worker进程。每个worker进程都常驻内存,并且随着请求数量的增多可以动态的增加,就是所谓的prefork动态增加;且每个worker进程都在达到最大的处理请求之后会自动重启,如果进程没有达到最大的请求数量而意外挂掉,则master会重启该worker进程。
虽然,worker进程的数量可以随着请求数量的增加predork动态增加,但是由于资源的有限,worker进程的数量存在一个上限---pm.max_children,达到这个上限之后,如果还有额外的请求且worker进程都处于繁忙状态,那么该请求会被master进程挂起到连接队列backlog中(默认值为511)。
在项目中Nginx和PHP-FPM是一种很好的解耦方式,Nginx的优势之处在于处理高并发,所有的静态界面的处理都由Nginx处理;而PHP-FPM专注于处理php请求,每个请求对应一个php的页面,这样实现了动态界面和静态界面处理的分离,减轻了Nignx的负担。
5. 总结
这篇文章对CGI和FastCGI的工作方式简单的介绍,并对二者的工作方式做了简要的对比,FastCGI由于worker进程常驻内存,能够很大程度上提高并发性。最后对采用FastCGI的PHP-FPM做了简要的介绍。