Browser
请求http://xxx.com/aa.html
-> Web Server(Nginx/Apache)分发 -> 找到aa.html
文件返回给Browser
。
Browser
请求http://xxx.com/bb.php
-> Web Server(Nginx/Apache)分发 -> PHP解析器
(PHP-CGI
程序)-> 返回处理结果给Web Server -> 返回数据给Browser。
原理:服务器根据配置文件
,知道这是一个PHP
脚本文件,需要去找PHP解析器
来处理。
PHP解析器
会解析php.ini
文件初始化执行环境,然后处理请求,再以标准的数据格式返回处理结果,最后退出进程。
image
CGI
是服务器与后台语言交互的协议,有了这个协议,开发者可以使用任何语言处理服务器转发过来的请求,动态地生成内容,保证了传递过来的数据是标准格式
的(规定了以什么样的格式传哪些数据(URL、查询字符串、POST数据、HTTP header等等
)),方便了开发者。
PHP语言
对应与服务器交互的CGI程序
就是PHP-CGI
。
CGI程序
本身只能解析请求
、返回结果
,不会进程管理
,所以有一个致命的缺点,那就是每处理一个请求都需要fork
一个全新的进程,随着Web
的兴起,高并发越来越成为常态,这样低效的方式明显不能满足需求(每一次web请求
都会有启动和退出进程
,也就是最为人诟病的fork-and-execute
模式,这样一在大规模并发下,就死翘翘了)。
就这样,FastCGI
诞生了,CGI程序
很快就退出了历史的舞台。
FastCGI
,顾名思义就是更快的CGI程序
,用来提高CGI程序
性能,它允许在一个进程内处理多个请求
,而不是一个请求处理完毕就直接结束进程,性能上有了很大的提高。
CGI程序
的性能问题在哪呢?PHP解析器
会解析php.ini
文件,初始化执行环境,就是这里了。
标准的CGI程序
对每个请求都会执行这些步骤(不闲累啊!启动进程很累的说!),所以处理每个请求的时间会比较长。这明显不合理嘛!
FastCGI
是怎么做的呢?首先,FastCGI
会先启一个master进程
,解析配置文件,初始化执行环境,然后再启动多个worker进程
。当请求过来时,master
会传递给一个worker
,然后立即可以接受下一个请求。
这样就避免了重复的劳动,效率自然是高。
而且当worker
不够用时,master
可以根据配置预先启动几个worker
等着。
当然空闲worker
太多时,也会停掉一些,这样就提高了性能,也节约了资源。这就是FastCGI
的对进程的管理。
ps:也有一些能够调度PHP-CGI进程
的程序,比如说由lighthttpd
分离出来的spawn-fcgi
。好了,PHP-FPM
也是这么个东东,在长时间的发展后,逐渐得到了大家的认可(要知道前几年大家可是抱怨PHP-FPM
稳定性太差的),也越来越流行。
image
它是FastCGI协议
的一个实现,任何实现了FastCGI协议
的服务器都能够与之通信。
FPM
之于标准的FastCGI程序
,也提供了一些增强功能,具体可以参考官方文档:PHP: FPM Installation。
FPM
是一个PHP进程管理器
,包含master
和worker
两种进程。master进程
只有一个,负责监听端口,接收来自服务器的请求,而worker进程
则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个PHP解释器
,是PHP代码
真正执行的地方,下面是我本机上FPM
的进程情况:1个master进程
,2个worker进程
。
$ ps -ef | grep fpm
root 130 1 0 01:37 ? 00:00:01 php-fpm: master process (/usr/local/php/etc/php-fpm.conf)
php-fpm 131 130 0 01:37 ? 00:00:00 php-fpm: pool www
php-fpm 133 130 0 01:43 ? 00:00:00 php-fpm: pool www
FPM
接收到请求,到处理完毕,其具体的流程如下:FPM
的master进程
接收到请求。master进程
根据配置指派特定的worker进程
进行请求处理,如果没有可用进程,返回错误,这也是我们配合Nginx
遇到502
错误比较多的原因。worker
进程处理请求,如果超时,返回504
错误。FPM
从接收到处理请求的流程就是这样了,那么Nginx
又是如何发送请求给FPM
的呢?这就需要从Nginx
层面来说明了。
我们知道,Nginx
不仅仅是一个Web服务器
,也是一个功能强大的Proxy服务器
,除了进行http请求
的代理,也可以进行许多其他协议请求的代理,包括本文与FPM
相关的FastCGI协议
。为了能够使Nginx
理解FastCGI协议
,Nginx
提供了FastCGI模块
来将http请求
映射为对应的FastCGI
请求。
Nginx
的FastCGI模块
提供了fastcgi_param指令
来主要处理这些映射关系
,下面 是Nginx
的一个配置文件实例,其主要完成的工作是将Nginx
中的变量翻译成PHP
中能够理解的变量。
$ cat /usr/local/nginx/conf/fastcgi.conf
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
除此之外,非常重要的就是fastcgi_pass指令
了,这个指令用于指定FPM进程
监听的地址,Nginx
会把所有的PHP请求
翻译成FastCGI请求
之后再发送到这个地址。下面一个简单的可以工作的Nginx配置文件
:
server {
listen 80;
server_name test.me;
root /usr/local/web/myproject/public;
index index.php index.html index.htm;
access_log /usr/local/nginx/logs/test-access.log;
error_log /usr/local/nginx/logs/test-error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~\.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/web/myproject/public/$fastcgi_script_name;
fastcgi_pass unix:/usr/local/php/var/run/php-fpm.sock;
fastcgi_index index.php;
}
}
在这个配置文件中,我们新建了一个虚拟主机
,监听80端口
,项目根目录
为 /usr/local/web/myproject/public
。然后我们通过location指令
,将所有的以.php结尾
的请求都交给FastCGI模块
处理,从而把所有的PHP请求
都交给了FPM
处理,从而完成Nginx
到FPM
的闭环。
如此以来,Nginx
与FPM
通信的整个流程应该比较清晰了。
image
php.ini
配置文件后,使用PHP-FPM
为什么能平滑重启?修改php.ini
之后,PHP-CGI进程
是没办法平滑重启的。
PHP-FPM
对此的处理机制是新的worker进程
用新的配置,已经存在的worker进程
处理完手上的活就可以歇着了,通过这种机制来平滑过渡
。