mod_php VS mod_fastcgi
目录
什么是mod_php和mod_fastcgi
在lamp体系中,对于apache端php的配置,我们最常用的就是mod_php, 它把PHP做为APACHE一个内置模块。让apache http服务器本身能够支持PHP语言,不需要每一个请求就启动PHP解释器来解释PHP。
和把webserver与php绑定起来的方式不同,fastcgi是HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,相当于一个程序接口。它可以接受来自web服务器的请求,解释输入信息,将处理后的结果返回给服务器(apache,lighty等)。mod_fastcgi就是在apache下支持fastcgi协议的模块。
工作原理
在介绍这两种模式的工作原理前,我们先了解一下php的架构
图1 php架构图
上面是php的架构图,从图上可以看到, SAPI提供了一个和外部通信的接口,使得PHP可以和其他应用进行交互数据。php默认提供了很多种SAPI,常见的给apache的mod_php5,CGI,给IIS的ISAPI,还有Shell的CLI。对于一个基于apache的php应用,其运行流程可以简单归结如下
apache -> httpd -> mod_phpfastcgi -> sapi -> php
我们下面提到的两种工作方式就分别用到了mod_php5和cgi的sapi。
mod_php
mod_php是在lamp体系中最常使用的工作方式,在这种模式下,php被编译为apache的一个内置模块,在启动时加载。当有一个php请求过来时,直接在httpd进程里完成php的解释运行,将结果返回。
在php的sapi中,有这样一个函数: sapi_cgibin_ub_write, 这个函数告诉了Zend,如何输出数据。查看mod_php的sapi源码,我们会发现,这个函数直接调用了apache的ap_rwrite函数。所以,用mod_php,我们可以把php和apache看做一个模块,两者绑定在一起。其工作原理如下图所示
图2 mod_php工作原理
mod_fastcgi
在说明fastcgi之前,先了解一下普通cgi的工作流程:
web server收到用户请求,并把请求提交给cgi程序,cgi程序根据请求提交的参数作应处理,然后输出标准的html语句返回给web server,web server再返回给客户端,这就是普通cgi的工作原理。
fastcgi是基于cgi架构的扩展,他的核心思想就是在web server和具体cgi程序之间建立一个智能的可持续的中间层,统管cgi程序的运行,这样web server只需要将请求提交给这个层,这个层再派生出几个可复用的cgi程序实例,然后再把请求分发给这些实例,这些实例是可控的,可持续,可复用的, 因此一方面避免了进程反复fork,另一方面又可以通过中间层的控制和探测机制来监视这些实例的运行情况,根据不同的状况fork或者回收实例,达到灵活 性和稳定性兼得的目的。modFastCGI的主要优点是把动态语言和web server分离开来。这种技术允许把web server和动态语言运行在不同的主机上,以大规模扩展和改进安全性而不损失生产效率。
基于mod_fastcgi方式的php应用,其典型工作流程如下
图3 mod_fastcgi工作原理
从图上可以看出, apache启动后,mod_fastcgi会在启动多个cgi程序,也就是php-cgi脚本。具体脚本的数目通过配置来指定。当有http请求到来后,httpd进程会选择一个当前空闲的一个php-cgi程序来执行,执行的方式和mod_php类似,也是通过php-cgi提供的sapi完成交互。查看源码,可以发现对于cgi的sapi,和mod_php不同,它是把结果输出到fastcgi提供的stdout上,fastcgi再将数据返回给httpd完成交互。
mod_factcgi的三种配置方式
基于mod_fastcgi的fastcgi应用一共有三种配置方式:静态、动态和远程。他们通过apache配置中的伪指令来代替,对应于三种方式的伪指令分别是FastCgiServer, FastCgiConfig, 以及FastCgiExternalServer。
对于静态和动态这两种方式,apache将通过mod_fastcgi自带的进程管理工具(fcgi-pm)来管理fastcgi应用程序,也就是php-cgi。fcgi-pm在apache启动时就被自动激活了。相对于前两种,远程模式下,php-cgi不由fcgi-pm来管理,apache不会去关心php-cgi程序的状态。
下面详细说一下三种配置方式
静态配置
通过FastCGIServer 伪指令将文件名定义为静态 FastCGI 应用程序。初始时需要指定启动的php-cgi进程数目,默认为1个。apache运行过程中,如果静态应用程序实例因为任何原因死了,那么fcgi-pm将衍生另一个实例来替换。
语法 FastCgiServer file name [options]
重要参数说明
idle-timeout n(30 秒)。在请求异常终止和事件记录在 error LogLevel 前,所允许的 FastCGI 应用程序不活动秒数。仅当存在与 FastCGI 应用程序的暂挂连接时,该不活动计时器才应用。如果应用程序在此期间不响应队列的请求,那么请求异常终止。如果与应用程序的通信完成,但是与客户机的通信未完成(缓存的响应),那么超时不应用。
processes n(1)。在服务器初始化时衍生的应用程序实例数。
port n无。应用程序用于与 Web 服务器通信的 TCP 端口号(1-65535)。此选项使应用程序可以从网络中的其他机器访问。-socket 选项和 -port 选项是互斥的。
socket filename unix sock文件名
Restart-delay n(5 秒)。此应用程序的失败实例重新衍生之间的最小秒数。此延迟阻止中断的应用程序使用过多系统资源。
动态配置
在静态配置中,我们初始就指定了要启动的php进程数。和静态的相反,动态应用程序根据需求启动。php应用实例的数目根据当前http请求数目动态变化,进程的管理也是通过fcgi-pm来完成。
语法 FastCgiConfig option [option ...]
重要参数说明
和静态方式相比,动态配置的参数主要增加了以下几个
maxProcesses n在任何时候允许运行的最大动态 FastCGI 应用程序实例数。
minProcesses n任何时候允许运行,且无须由fcgi-pm(因没有需求)杀死的最小动态 FastCGI 应用程序实例数。
在dyn模式下,因为fastcgi实例数目是动态改变的。所以没有静态方式中process这个参数
远程方式
这种模式下,fastcgi实例和apache分离开来,这两者可以分开来部署。他们之间的通信通过tcp或者unix sock来完成。使用ext方式,fastcgi实例将不会由fcgi-pm来管理,而是独立的运行。
语法 - FastCgiExternalServer 文件名 -host 主机名端口 [-appConnTimeout n]
FastCgiExternalServer 文件名 -socket 文件名 [-appConnTimeout n]
重要参数说明
idle-timeout n(30 秒) 在请求异常终止,并且事件记录之前,允许 FastCGI 应用程序保持不活动的秒数。只有当与 FastCGI 应用程序的连接暂挂时,此不活动定时器才适用。如果请求进入应用程序的请求队列,而此应用程序在此期间没有响应(通过写和刷新),则此请求将异常终止。如果与应用程序的通信已完成,而与客户机的通信尚未完成(响应被高速缓存),则此超时不适用。
host host:port应用程序用于与 Web 服务器通信的主机名或 IP 地址和 TCP 端口号 (1-65535)。-socket 和 -host 选项是互斥的。
socket文件名 无
应用程序用于与 Web 服务器通信的 UNIX 域套接字的文件名。此文件名相对于 FastCgiIpcDir。-socket 和 -port 选项是互斥的。
php-fpm
使用FastCGI,最主要优点是把应用和web server(apache)分离开来。这样允许把web server和动态语言(php)运行在不同的主机上,以大规模扩展和改进安全性而不损失效率。
这样的情况下,对于php-cgi程序,由于从apache中分离出来,就需要一个单独的工具来对这些进程进行管理,在stc和dyn两种模式下,mod_fastcgi中自带的fcgi-pm会充当了这样的角色。可是,ext模式下却没有。我们只能通过一个脚本静态的启动n个实例,一旦进程死掉,还需要手工重启。当然,你可以使用supervise来管理这些进程,但是毕竟不那么灵活而且也仅仅一部份问题。甚至于fcgi-pm,因为实现的原因,其本身也有很多问题,例如不太稳定,压力下出core,无法平滑的完成切换等。
基于上述的原因,我们需要一个稳定可靠的进程管理工具,就像lighty下的spwn-cgi。幸运的是,出现了php-fpm。它是一个类似于spwn-cgi的管理工具,可以和任何支持远端FastCGI的web server工作。在官方的手册上,列举了以下php-fpm所具有的特性:
特性 | Php自带的 | Spwn-cgi | Php-fpm |
php守护程序: pid file, log file, setsid(), setuid(), setgid(), chroot() |
(-) |
(-) |
(+) |
进程控制,可以平滑地重启、重新载入配置和二进制模块而不丢失请求 |
Php4 (-) Php5 只能平滑停止 |
(-) |
(+) |
限制ip地址,可以满足web server的要求 |
php4 (-) php5.2.2 > (+) |
(-) |
(+) |
如果使用优化器,在遇到opcode缓存随机损坏的时候紧急重启所有进程 |
(-) |
(-) |
(+) |
使用用不同的uid / gid / chroot / 环境变量,不同的 php.ini 选项,不需要safe mode |
(-) |
(-) |
(+) |
记录work process的stdout和stderr |
(-) |
(-) |
(+) |
如果set_time_limit没有起作用,强制结束过期进程 |
(-) |
(-) |
(+) |
特色功能 Error header、优化的上传支持、fastcgi_finish_request()
另外,php-fpm还可以兼容zend Optimize,各种缓存优化器。
Php-fpm的安装
Php-fpm是以patch的方式安装的,如果要使用它,你必须在安装php前打上这个补丁。
eg:
gzip -cd php-5.2.6-fpm-0.5.9.diff.gz | patch -d php-5.2.6 -p1
在configure时加上—enable-fpm选项。
安装完php后,会有以下几个文件
$prefix/ext/php-fpm.conf
$prefix/log/php-fpm.log
$prefix/log/php-fpm.pid
其中php-fpm.conf是配置文件,具体如何配置文件里有非常详尽的注释。
最后,执行./bin/php-cgi –fpm &就可以了。
另外,你也可以使用$prexif/sbin/php-fpm脚本来方便的维护。
性能测试
名称解释
ab
apache自测的性能测试工具,主要用于测试极限压力:对于同一url每秒所能执行的次数及响应时间。
myab
baidu开发的压力测试工具,区别于ab,其更主要的功能是测试指定压力条件下机器的负载情况。
eacc
一种php加速器,主要是将php程序编译后的结果缓存起来。加速php的执行,对性能有很大幅度的提升 ,更详细的情况可以参见另一篇关于php缓存优化的文档 《缓存优化工具-php加速的利器》
etc、stc、dyn
mod_fastcgi运行的模式,分别指远程、静态和动态模式
测试内容
测试中我们将针对不同的运行模式和页面类型进行极限压力测试及机器负载测试。同时,我们还将测试使用缓存优化和不使用缓存优化下机器的表现情况。另外,由于我们的测试主要是对比两种模式的性能差别,因此对于机器的硬件配置不是特别敏感,测试的时候只需要保证机器负载为0即可。
测试结果
- 最简单的php页面
压力工具:mysql
压力情况:每秒1000次请求,并发数70。
性能指标:cpu idle
不同模式下机器idle情况如下图
图4最简单php页面,1000/s请求 idle情况
从图上我们可以看出,测试最简单的php页面时,各种模式下性能几乎没有差别,且由于页面非常简单,eacc的效果也基本没有。
- 复杂的php程序(2000行代码+)
压力工具:mysql
压力情况:每秒400次请求,并发数70
性能指标:cpu idle
测试结果如下图
图5复杂的php代码,400/s压力idle情况
从图上可以看出,在php代码复杂的情况下,前面4中模式性能差别不大,mod_php略优,然后fastcgi动态配置的方式在此情况下性能差距非常之大,idle直接降为0,同时虽然每秒是400次的压力,其只能处理200次左右的请求。
由于php代码很复杂,eacc的作用明显体现出来,使用缓存优化后性能提升一倍以上。
另外,还可以看出,eacc这样的缓存优化工具对于mod_php和fastcgi方式起到的作用基本相同,他们之间的性能差异和使用前维持相同。
在上述情况下,如果不适应fpm,fastcgi模式会出core,主要原因还在在于进程管理的问题。
- 实际项目中的php程序
这里我们采用群组项目中的pb页面,涉及到和数据库交互,db交互2次。db部署在其他机器,每次请求耗时50ms左右。
压力工具:mysql
压力情况:每秒400次请求,并发数70
性能指标:cpu idle
测试结果如下
图6实际的php页面,400/s压力idle情况
从图上可以看出,性能的情况和图2类似,各种模式的差距仍然不大。
- 极限压力测试
压力工具:ab
测试参数:并发请求数100,总次数10000次。
测试页面:pb页面
性能指标:每秒请求数 rps
图7极限压力测试 pb页面
从上图我们可以看到,极限压力下,使用eacc后,mod_php和ext(fpm)基本差不多,保持在860/s左右的水平,而使用fpm方式进行管理的模式略优于其他两种fastcgi模式。
测试中发现,fastcgi模式下,不使用php-fpm管理的话,仍然会有core出现。
fastcgi配置项对性能的影响。
在mod_fastcgi下,不管是ext还是stc方式,有一个配置项是非常关键的:默认启动的php-cgi进程实例数
从fastcgi原理可以看出,当有一个httpd进程到来时,它需要调用fastcgi server来执行,如果此时所有server都出于服务状态,则这个httpd进程将出于等待状态得不到服务,导致请求无法响应。
对pb页面进行测试,配置不同的实例数目进行极限压力测试,得到结果如下
图8 fastcgi实例数对性能的影响
分析一下这个原因:
我们的pb页面执行时间是50ms,也就是说一个php-cgi程序在1s内可以处理的请求数目是1000/50 = 20。假设启动的实例数目是n,则1s内最多能完成的请求数为n*20,从图上可以看出,实际结果符合我们的计算。另外,这个数值也不是无限增大了,当n*20大于server本身所能承受的极限时,rps也不会继续往上增长了。
所以,使用fastcgi,对于n的选择,要综合页面平均处理时间,最大压力等多种因素结合来配置。
同时,在ext方式下,webserver和fastcgi server交互的方式有两种:通过tcp或者unix sock。测试一下这两种方式的情况
使用myab每秒400次请求pb页面,机器idle
图9 ext下unix sock和tcp的区别
从图上可以看出,不管是否使用eacc,tcp方式的idle都有一个百分点的下降,这个差异主要是因为多了tcp连接过程造成的。
分析&结论
根据各种测试结果,可以看出和fastcgi方式相比,mod_php的性能在各种情况下都稍优,这种差异主要是在于后一种方式增加了一次数据交互过程php->fascgi->apache。但是这个差距并不大,在使用了eacc等缓存优化工具后,性能有了很大提升。他们之间的差距完全不是瓶颈。从使用的角度来说,fastcgi具有以下优点:
- webserver和php程序分离,两者可以部署在不同的地方,通过socket方式通信带来一定安全性
- 使用fastcgi,在出现问题时可以更好的定位是webserver还是php的原因
- fastcgi方式不依赖于webserver,更加灵活,扩展性也更好
- fastcgi本身会有一些进程监控和日志记录,更便于分析问题,跟踪状态。
- 灵活多样的配置,可以根据实际的应用进行合理配置达到最佳效果。
当然fastcgi也有一些缺点
- mod_fastcgi在进程管理上有一些问题,容易出core。这个问题通过使用php-fpm可以解决
- 由于fastcgi应用单独分离出来,因此需要单独监控进程的状态。防止进程挂掉后导致服务出现问题,这个可以通过使用用supervise管理一定程度上避免这个问题。
- 文档相对缺乏,mod_fastcgi对apache的支持也不是特别好,且基本没有升级。
关于fastcgi运行方式的选择,从效率、稳定性等各方面来说,ext方式是最佳选择了。而且考虑到我们可能会将webserver和php分开到不同机器,选择远程方式也是必须的。
进程管理工具,从各方面来说,php-fpm是最优选择了,即时使用lighty作用web server,也完全可以用它代替spwn-cgi。
综合测试结果和上述分析,我们完全可以采用fastcgi代替传统的mod_php。推荐使用下面的组合方式
apache + ext + php-fpm(with superwise)
如果webserver和fastcgi部署在同一机器上,使用unix sock方式通信,否则使用tcp方式。
附录
apache+mod_fastcgi+php搭建
- php
mod_fastcgi的搭建主要有三种方式
stc、ext和dyn,不管是哪种方式,首先在安装php的时候需要加上如下选项
--enable-fastcgi,并且不能使用—with-apxs。
以下是一个配置php例子
./configure \
--prefix=/home/club/hongdk/env/php5.26-fcgi/ \
--enable-trace-vars \
--with-zlib-dir=/home/club/hongdk/tool/zlib/ \
--with-mysql=/home/club/hongdk/env/mysql5/ \
--with-mysqli=/home/club/hongdk/env/mysql5/bin/mysql_config \
--with-gettext \
--with-iconv \
--enable-mbstring=gbk \
--with-xmlrpc \
--enable-safe-mode \
--enable-sockets \
--enable-url-fopen-wrapper \
--enable-ftp \
--enable-shmop \
--with-config-file-path=/home/club/hongdk/env/php5.26-fcgi/ \
--enable-xml \
--with-dom=/home/club/hongdk/tool/libxml/ \
--with-libxml-dir=/home/club/hongdk/tool/libxml/ \
--with-curl=/home/club/hongdk/tool/curl \
--with-curlwrappers \
--enable-fastcgi \
在安装完后,会在php的bin目录下找到如下文件php-cgi
执行 ./php-cgi –v,如果看到
PHP 5.2.5 (cgi-fcgi) (built: Nov 12 2008 20:44:08)
Copyright (c) 1997-2007 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies
就表明安装成功了
- mod_fastcgi
cd mod_fastcgi-2.4.6
cp Makefile.AP2 Makefile
vi Makefile
修改top_dir为apache安装目录
make
make install
- apache配置
修改httpd.conf
首先增加
LoadModule fastcgi_module modules/mod_fastcgi.so
然后根据不同的运行模式进行配置
eg
ext模式
<IfModule mod_fastcgi.c>
FastCgiExternalServer $php-server -socket /home/club/fastcgi.sock
ScriptAlias /cgi-bin/ "/home/club/hongdk/env/apache-fcgi/cgi-bin/"
AddHandler php-fastcgi .php
Action php-fastcgi /cgi-bin/php-cgi
AddType application/x-httpd-php .php
</IfModule>
其中$php-sever为php-cgi程序,例如/home/club/hongdk/env/apache-fcgi/cgi-bin/php-cgi
stc模式
<IfModule mod_fastcgi.c>
FastCgiServer $php-server -processes 10
ScriptAlias /cgi-bin/ "/home/club/hongdk/env/apache-fcgi/cgi-bin/"
AddHandler php-fastcgi .php
Action php-fastcgi /cgi-bin/php-cgi
AddType application/x-httpd-php .php
</IfModule>
dyn模式
<IfModule mod_fastcgi.c>
FastcgiConfig –idle-timeout 120
ScriptAlias /cgi-bin/ "/home/club/hongdk/env/apache-fcgi/cgi-bin/"
AddHandler php-fastcgi .php
Action php-fastcgi /cgi-bin/php-cgi
AddType application/x-httpd-php .php
</IfModule>
然后,定义fastcgi server也就是我们的php-cgi程序所在文件夹的权限
<Directory $php-server>
Options ExecCGI
Order allow,deny
Allow from all
</Directory>
- fastcgi应用
在我们的应用中就是php-cgi
如果是stc和dyn方式,不需要单独再启动php-cgi,他们的管理有mod_fastcgi自带的fcgi-pm来完成
在etc模式下,我们需要单独启动php-cgi程序
可以用php-fpm来完成。也可以独立的使用一个脚本来启动,例如
#!/bin/sh
exec &> /dev/null
exec < /dev/null
PHP_FCGI_CHILDREN=32
export PHP_FCGI_CHILDREN
exec /home/club/hongdk/env/apache-fcgi/cgi-bin/php-cgi -b ./xl.sock -c /home/club/hongdk/env/php5-fcgi/php.ini
推荐使用php-fpm来完成