PHP-CGI 详解

CGI程序本身只能解析请求、返回结果,不会进程管理,所以有一个致命的缺点,那就是每处理一个请求都需要fork一个全新的进程,随着Web的兴起,高并发越来越成为常态,这样低效的方式明显不能满足需求(每一次web请求都会有启动和退出进程,也就是最为人诟病的fork-and-execute模式,这样一在大规模并发下,就死翘翘了)。就这样,FastCGI诞生了,CGI程序很快就退出了历史的舞台。

FastCGI (Fast CGI)
FastCGI,顾名思义就是更快的CGI程序,用来提高CGI程序性能,它允许在一个进程内处理多个请求,而不是一个请求处理完毕就直接结束进程,性能上有了很大的提高。

  • 提高性能?那么CGI程序的性能问题在哪呢?

PHP解析器会解析php.ini文件,初始化执行环境,就是这里了。标准的CGI程序对每个请求都会执行这些步骤(不闲累啊!启动进程很累的说!),所以处理每个请求的时间会比较长。这明显不合理嘛!首先,FastCGI会先启一个master进程,解析配置文件,初始化执行环境,然后再启动多个worker进程。当请求过来时,master会传递给一个worker,然后立即可以接受下一个请求。
这样就避免了重复的劳动,效率自然是高。而且当worker不够用时,master可以根据配置预先启动几个worker等着。当然空闲worker太多时,也会停掉一些,这样就提高了性能,也节约了资源。这就是FastCGI的对进程的管理。

PS:也有一些能够调度PHP-CGI进程的程序,比如说由lighthttpd分离出来的spawn-fcgi。好了,PHP-FPM也是这么个东东,在长时间的发展后,逐渐得到了大家的认可(要知道前几年大家可是抱怨PHP-FPM稳定性太差的),也越来越流行。

PHP-FPM(FastCGI Process Manager)

它是FastCGI协议的一个实现,任何实现了FastCGI协议的服务器都能够与之通信。FPM之于标准的FastCGI程序,也提供了一些增强功能,具体可以参考官方文档:PHP: FPM Installation

  • FPM是一个PHP进程管理器,包含master和worker两种进程。

master进程只有一个,负责监听端口,接收来自服务器的请求,而worker进程则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个PHP解释器,是PHP代码真正执行的地方,下面是我本机上FPM的进程情况:1个master进程,2个worker进程。

  • 从FPM接收到请求,到处理完毕,其具体的流程如下:
  1. FPM的master进程接收到请求。
  2. master进程根据配置指派特定的worker进程进行请求处理,如果没有可用进程,返回错误,这也是我们配合Nginx遇到502错误比较多的原因
  3. worker进程处理请求,如果超时,返回504错误
  4. 请求处理结束,返回结果。
  • FPM从接收到处理请求的流程就是这样了,那么Nginx又是如何发送请求给FPM的呢?

我们知道,Nginx不仅仅是一个Web服务器,也是一个功能强大的Proxy服务器,除了进行http请求的代理,也可以进行许多其他协议请求的代理,包括本文与FPM相关的FastCGI协议。为了能够使Nginx理解FastCGI协议Nginx提供了FastCGI模块来将http请求映射为对应的FastCGI请求
Nginx的FastCGI模块提供了fastcgi_param指令来主要处理这些映射关系,下面是Nginx的一个配置文件实例,其主要完成的工作是将Nginx中的变量翻译成PHP中能够理解的变量。

除此之外,非常重要的就是fastcgi_pass指令了,这个指令用于指定FPM进程监听的地址,Nginx会把所有的PHP请求翻译成FastCGI请求之后再发送到这个地址。下面一个简单的可以工作的Nginx配置文件:

在这个配置文件中,我们新建了一个虚拟主机,监听80端口,项目根目录为 /usr/local/web/myproject/public。然后我们通过location指令,将所有的以.php结尾的请求都交给FastCGI模块处理,从而把所有的PHP请求都交给了FPM处理,从而完成Nginx到FPM的闭环。如此以来,Nginx与FPM通信的整个流程应该比较清晰了。

  • 修改了php.ini配置文件后,使用PHP-FPM为什么能平滑重启?

修改php.ini之后,PHP-CGI进程是没办法平滑重启的。PHP-FPM对此的处理机制是新的worker进程用新的配置,已经存在的worker进程处理完手上的活就可以歇着了,通过这种机制来平滑过渡。

Opache详解

Opcache的前生是Optimizer+, 它是PHP官方公司Zend开发的一款闭源但可以免费使用的PHP优化加速组件。
Optimizer+将PHP代码预编译生成的脚本文件Opcode缓存在共享内存中供以后反复使用,从而避免了从磁盘读取代码再次编译的时间消耗。
同时,它还应用了一些代码优化模式,使得代码执行更快,从而加速PHP的执行。

request请求(nginx, apache等)-> Zend引擎读取.php文件-> 扫描其词典和表达式(词法分析)->解析文件->创建要执行的计算机代码(称为Opcode)->最后执行Opcode->response返回
每一次请求PHP脚本都会执行一遍以上步骤,如果PHP源代码没有变化,那么Opcode也不会变化,显然没必要每次都重新生成Opcode, 结合在Web中无所不在的缓存机制,我们可以把Opcode缓存下来,以后直接访问缓存的Opcode岂不是更快,启用Opcode缓存之后的流程图如下所示:

Opcode cache 的目的是避免重复编译,减少CPU和内存开销。
PS: 当解释器完成对脚本代码的分析后,便将它们生成可以直接运行的中间代码,也称为操作码(Operate Code, opcode), 动态内容的性能瓶颈不在cpu和内存,而在I/O操作,比如数据库查询带来的I/O开销,那么opcache的性能提升非常有限,但总归是好的。
Opcode原理
Opcode 是一种PHP脚本编译后的中间语言,就像Java的ByteCode, 或者.NET的MSL。举个例子,比如你写个php代码:

<?php   
echo “Hello World”;  
 $a = 1 + 1;   
echo $a;
?>

PHP(PHP引擎Zend)执行这段代码会经过如下4个步骤:
1.Scanning , 将PHP代码转换为语言片段(Tokens)
2.Parsing, 将Tokens转换成简单而有意义的表达式
3.Compilation,  将表达式编译Opcodes
4.Execution, 顺次执行Opcodes, 每次一条,从而实现PHP脚本功能。
每次请求来,就不需要重复执行前面三步,从而大幅提高PHP执行速度。


Opcode是php语言里供zend引擎执行的一种中间代码,类似java中的字节码,或者python中的字节码对象pycodeobject.