理解Nginx与PHP-FPM通信工作机制(非原创)

声明: 我这篇博客是手抄  理解 Nginx 与 PHP-FPM 通信的工作机制  ,目的是加强我的记忆。

还有一更详细的, 深入剖析 Web 服务器与 PHP 应用之间的通信机制 - 掌握 CGI 和 FastCGI 协议的运行原理

浏览器请求网页的过程

 请求静态页面

Borwser 请求 index.html -》 Web server (nginx/apache)分发,找到index.html 文件直接返回给Browser。

请求动态脚本

Browser 请求 index.php -》 Web server(nginx/apache)分发-》 php解析器(php-CGI 程序)-》返回处理结果给Web server -》 返回给 Browser。

原理是,服务器根据配置文件,知道这是一个脚本文件,然后需要去找php解析器来处理,php解析器会根据解析php.ini文件初始化执行环境,然后处理请求,再以标准的数据格式返回处理结果,最后退出进程。

CGI到FPM进化史  

CGI(Common Gateway Interface)

CGI是服务器与后台语言(php/python)交互的协议,有了这个协议,开发者可以使用任何语言来处理服务器转发过来的请求,动态地生成内容,保证了传递过来的数据是标准格式的,即规定了以什么样的格式传哪些数据,包括URL、查询字符串、POST数据、HTTP header等等,方便了开发者。

PHP-CGI

php语言对应与服务器交互的CGI程序就是PHP-CGI 。

CGI程序本身只能解析请求、返回结果,不会进程管理,所以有一个致命的缺点,那就是每处理一个请求都会fork一个全新的进程,随着web的兴起,高并发越来越成为常态,这样的方式显然不能满足需求,每一次web请求都会有启动和退出,也就是最为人们诟病的fork-and-execute模式,这样在大并发的情况下机器就死翘翘了。

于是,FastCGI诞生了,CGI程序很快退出历史舞台。

FastCGI

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

具体地,FastCGI是如何提高性能的呢?先看CGI程序的性能问题出在哪儿。

php解析器会解析php.ini文件,初始化执行环境,就是这里了。标准的CGI程序对每个请求都会执行这些步骤,所以每个请求的时间都会bijiaochang。

那么FastCGI是怎么做的呢?

首先,FastCGI会先启动一个master进程,解析php.ini配置文件,初始化执行环境,然后再启动多个worker进程。当有请求过来的时候,master会把请求传递给worker,然后立即可以处理下一个请求。这样避免了重复的劳动,效率自然提高了。

而且,当worker不够用时,master可以根据配置预先启动几个workder等着。当空闲的worker太多时,也会主动停掉一些,这样就提高了性能也节约了资源。这就是FastCGI对进程的管理。

补充: 也有一些能够调度PHP-CGI的程序,比如说说由lighthttpd分离出来的spawn-fcgi。当然,PHP-FPM也是这样的程序,在长期的发展后,逐渐得到了大家都认可,现在非常流行。

 理解Nginx与PHP-FPM通信工作机制(非原创)

理解Nginx与PHP-FPM通信工作机制(非原创)

 理解Nginx与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进程。

$ 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接收到请求,到处理完毕,具体流程如下:

  1. FPM的master进程接收到请求
  2. master进程根据配置指派给特定的worker进程进行请求处理,如果没有可用的进程,返回错误,这也是我们配合Nginx遇到502错误比较多的原因。
  3. worker进程处理请求,如果处理时间超时,则返回504错误
  4. 请求处理结束,返回结果。

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请求之后发送到这个地址,下面是配置文件:

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的通信流程就比较清晰了。

 理解Nginx与PHP-FPM通信工作机制(非原创)

最后,修改了php.ini配置文件后。使用PHP-FPM为什么可以平滑重启呢?

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

 最后的最后,我写这个的最初原因是想知道Nginx跟PHP-FPM有哪几种通信方式。

这个问题的答案是:两种。分别是 unix socket和TCP socket。

 unix socket 配置如下:

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;

}

unix socket是一种终端,可以使同一台操作系统上两个或多个进程进行数据通信。这种方式需要在Nginx配置文件中填写PHP-FPM的pid文件位置,效率比TCP socket高。 

 TCP socket配置如下:

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
}

tcp socket的优点是可以跨服务器,如果Nginx和FPM不在一台机器上只能通过这种方式。 

 优缺点比较:

先看通信图解,如下:

 理解Nginx与PHP-FPM通信工作机制(非原创)

 从这图中不难看出,unix socket 减少了不必要的TCP开销,TCP需要经过loopback,还要申请临时端口和TCP相关资源,但是 unix socket高并发时候并不稳定,连接数爆发时,会产生大量的长时缓存,在没有面向连接协议的支撑下,大数据包可能会直接出错不返回异常。TCP这样的面向连接的协议,多少可以保证通信的正确性和完整性。

建议: 如果是同一台机器上运行Nginx和FPM,并发量不超过1000,选择unix socket方式,这样可以避免一些检查操作、路由等。因此更快、更轻。 如果面临高并发业务,选择更可靠TCP socket,还可以做负载均衡,内核优化等运维手段提维持效率。

 最最最最后,既然说到web server和PHP的通信,顺便提一下我的新发现。 在我上一篇博文 keepalived+nginx高可用web架构实践  中我需要搭建一台web server,灵机一动决定用docker配置apache和PHP,分别下载两个镜像并启动容器,我需要关联两个容器。我找到一个配置,居然是apache通过FastCGI方式调用PHP的。因为以前配置apache+PHP都是直接加载PHP模块,配置如下:

LoadModule php7_module        modules/libphp7.so 


AddType application/x-httpd-php .php
<IfModule dir_module>
    DirectoryIndex index.html index.php
</IfModule

FastCGI配置如下:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so

<VirtualHost *:80>
    DocumentRoot "/www/mysite.com"
    ServerName mysite.com
    ServerAlias www.mysite.com
   ProxyRequests Off
   ProxyPassMatch ^/(.*\.php)$ fcgi://127.0.0.1:9000/www/mysite.com/$1
    <Directory "/www/mysite.com">
        Options none
        AllowOverride none
        Require all granted
    </Directory>
</VirtualHost>

相关推荐