理解 Nginx 和PHP-FPM

FastCGI模块模块允许nginx同FastCGI协同工作,并且控制哪些参数将被安全传递。

一、CGI和FastCGI简介

1、什么是CGI

CGI 是Web 服务器运行时外部程序的规范接口,按CGI 编写的程序可以扩展服务器功能。CGI 应用程序能与浏览器进行交互,还可通过数据库API 与数据库服务器等外部数据源进行通信,从数据库服务器中获取数据。格式化为HTML文档后,发送给浏览器,也可以将从浏览器获得的数据放到数据库中。几乎所有服务器都支持CGI,可用任何语言编写CGI。

传统CGI接口方式的主要缺点是性能很差,因为每次HTTP服务器遇到动态程序时都需要重新启动脚本解析器来执行解析,然后将结果返回给HTTP服务器。这在处理高并发访问时几乎是不可用的,另外传统的CGI接口方式安全性也很差,现在已经很少使用了。

2、什么是 FastCGI

FastCGI是一个可伸缩地、高速地在HTTP server和动态脚本语言间通信的接口。多数流行的HTTP server都支持FastCGI,包括Apache、Nginx和lighttpd等。同时FastCGI也被许多脚本语言支持,其中就有PHP。FastCGI是从CGI发展改进而来的。
FastCGI接口方式采用C/S结构,可以将HTTP服务器和脚本解析服务器分开,同时在脚本解析服务器上启动一个或者多个脚本解析守护进程。当HTTP服务器每次遇到动态程序时,可以将其直接交付给FastCGI进程来执行,然后将得到的结果返回给浏览器。
这种方式的优点:可以让HTTP服务器专一地处理静态请求或者将动态脚本服务器的结果返回给客户端,这在很大程度上提高了整个应用系统的性能。

二、Nginx+FastCGI运行原理

1、运行原理

Nginx不支持对外部程序的直接调用或者解析,所有的外部程序(包括PHP)必须通过FastCGI接口来调用。FastCGI接口在Linux下是socket(这个socket可以是文件socket,也可以是ip socket)。

wrapper:为了调用CGI程序,还需要一个FastCGI的wrapper(wrapper可以理解为用于启动另一个程序的程序),这个wrapper绑定在某个固定socket上,如端口或者文件socket。当Nginx将CGI请求发送给这个socket的时候,通过FastCGI接口,wrapper接收到请求,然后Fork(派生)出一个新的线程,这个线程调用解释器或者外部程序处理脚本并读取返回数据;接着,wrapper再将返回的数据通过FastCGI接口,沿着固定的socket传递给Nginx;最后,Nginx将返回的数据(html页面或者图片)发送给客户端。这就是Nginx+FastCGI的整个运作过程,如图1-3所示。
在这里插入图片描述
所以,我们首先需要一个wrapper,这个wrapper需要完成的工作:
1、通过调用fastcgi(库)的函数通过socket和ningx通信(读写socket是fastcgi内部实现的功能,对wrapper是非透明的)
2、调度thread,进行fork和kill
3、和application(php)进行通信

Nginx是个轻量级的HTTP server,必须借助第三方的FastCGI处理器才可以对PHP进行解析,因此其实这样看来nginx是非常灵活的,它可以和任何第三方提供解析的处理器实现连接从而实现对PHP的解析(在nginx.conf中很容易设置)。
FastCGI接口方式在脚本解析服务器上启动一个或者多个守护进程对动态脚本进行解析,这些进程就是FastCGI进程管理器,或者称为FastCGI引擎。 spawn-fcgi与PHP-FPM就是支持PHP的两个FastCGI进程管理器。因此HTTPServer完全解放出来,可以更好地进行响应和并发处理。

FastCGI 的主要优点是把动态语言和HTTP Server分离开来,所以Nginx与PHP/PHP-FPM经常被部署在不同的服务器上,以分担前端Nginx服务器的压力,使Nginx专一处理静态请求和转发动态请求,而PHP/PHP-FPM服务器专一解析PHP动态请求。

2、Nginx+PHP-FPM

PHP-FPM是管理FastCGI的一个管理器,它作为PHP的插件存在,在安装PHP要想使用PHP-FPM时在老php的老版本(php5.3.3之前)就需要把PHP-FPM以补丁的形式安装到PHP中,而且PHP要与PHP-FPM版本一致,这是必须的)

PHP-FPM其实是PHP源代码的一个补丁,旨在将FastCGI进程管理整合进PHP包中。必须将它patch到你的PHP源代码中,在编译安装PHP后才可以使用。

PHP5.3.3已经集成php-fpm了,不再是第三方的包了。PHP-FPM提供了更好的PHP进程管理方式,可以有效控制内存和进程、可以平滑重载PHP配置,比spawn-fcgi具有更多优点,所以被PHP官方收录了。

在./configure的时候带 –enable-fpm参数即可开启PHP-FPM。fastcgi已经在php5.3.5的core中了,不必在configure时添加 --enable-fastcgi了。老版本如php5.2的需要加此项。

3、配置信息

关于fastcgi的配置文件,目前fastcgi的配置文件一般放在nginx.conf同级目录下,配置文件形式,一般有两种:fastcgi.conf 和 fastcgi_params。不同的nginx版本会有不同的配置文件,这两个配置文件有一个非常重要的区别:

fastcgi_parames文件中缺少下列配置:
fastcgi_param SCRIPT_FILENAME document_rootfastcgi_script_name;

我们可以打开fastcgi_parames文件加上上述行,也可以在要使用配置的地方动态添加。使得该配置生效。

当我们安装Nginx和PHP-FPM完后,配置信息:

PHP-FPM的默认配置php-fpm.conf:

listen_address 127.0.0.1:9000 #这个表示php的fastcgi进程监听的ip地址以及端口
start_servers
min_spare_servers
max_spare_servers

Nginx配置运行php: 编辑nginx.conf加入如下语句:

location ~ \.php$ { 
    root html; 
    fastcgi_pass 127.0.0.1:9000; #指定了fastcgi进程侦听的端口,nginx就是通过这里与php交互的 
    fastcgi_index index.php;
    include fastcgi_params; 
    fastcgi_param SCRIPT_FILENAME /usr/local/nginx/html$fastcgi_script_name; 
}

Nginx通过location指令,将所有以php为后缀的文件都交给127.0.0.1:9000来处理,而这里的IP地址和端口就是FastCGI进程监听的IP地址和端口。
输入命令 netstat -nlpt|grep php-fpm 会得到:
tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN 1057/php-fpm
这里的127.0.0.1:9000 就是监听本机9000端口的意思。

4、整体工作流程:

1)、FastCGI进程管理器php-fpm自身初始化,启动主进程php-fpm和启动start_servers个CGI 子进程。主进程php-fpm主要是管理fastcgi子进程,监听9000端口。fastcgi子进程等待来自Web Server的连接。

2)、当客户端请求到达Web Server Nginx是时,Nginx通过location指令,将所有以php为后缀的文件都交给127.0.0.1:9000来处理,即Nginx通过location指令,将所有以php为后缀的文件都交给127.0.0.1:9000来处理。

3)FastCGI进程管理器PHP-FPM选择并连接到一个子进程CGI解释器。Web server将CGI环境变量和标准输入发送到FastCGI子进程。

4)、FastCGI子进程完成处理后将标准输出和错误信息从同一连接返回Web Server。当FastCGI子进程关闭连接时,请求便告处理完成。

5)、FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在 WebServer中)的下一个连接。

简单来说,当需要处理php请求时,nginx的worker进程会将请求移交给php-fpm的worker进程进行处理,也就是最开头所说的nginx调用了php,其实严格得讲是nginx间接调用php。

5、Nginx和PHP-FPM的通信方式

Nginx和PHP-FPM的进程间通信有两种方式

一种是TCP
一种是UNIX Domain Socket.

其中TCP是IP加端口,可以跨服务器。而UNIX Domain Socket不经过网络,只能用于Nginx跟PHP-FPM都在同一服务器的场景。
用哪种取决于你的PHP-FPM配置:

方式1:
php-fpm.conf: listen = 127.0.0.1:9000
nginx.conf: fastcgi_pass 127.0.0.1:9000;
方式2:
php-fpm.conf: listen = /tmp/php-fpm.sock
nginx.conf: fastcgi_pass unix:/tmp/php-fpm.sock;
其中php-fpm.sock是一个文件,由php-fpm生成,类型是srw-rw----。

UNIX Domain Socket可用于两个没有亲缘关系的进程,是目前广泛使用的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIX Domain Socket通讯的。这种通信方式是发生在系统内核里而不会在网络里传播。UNIX Domain Socket和长连接都能避免频繁创建TCP短连接而导致TIME_WAIT连接过多的问题。对于进程间通讯的两个程序,UNIX Domain Socket的流程不会走到TCP那层,直接以文件形式,以stream socket通讯。如果是TCP Socket,则需要走到IP层,对于非同一台服务器上,TCP Socket走的就更多了。

浏览器访问网页的过程

  • 请求静态页面
    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文件初始化执行环境,然后处理请求,再以标准的数据格式返回处理结果,最后退出进程。

CGI 程序到 FPM 进化史

在这里插入图片描述

FastCGI

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稳定性太差的),也越来越流行。
在这里插入图片描述

PHP-FPM(FastCGI Process Manager)

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

  • FPM是一个PHP进程管理器,包含masterworker两种进程。
    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层面来说明了。
    我们知道,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通信的整个流程应该比较清晰了。

在这里插入图片描述

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

原文链接:
https://www.jianshu.com/p/eab11cd1bb28, https://www.jianshu.com/p/da152c6fdfa6

猜你喜欢

转载自blog.csdn.net/bushiyao_/article/details/85247978