nginx 与 php-fpm 通信机制

1,564 阅读12分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

本文主要介绍 nginx 与 php-fpm 之间的通信机制,通过这篇文章:

  • 你能够了解到 nginx 与 php-fpm 之间通信的来龙去脉;

  • 您能够对 CGI、FastCGI、PHP 解析器等概念之间的关联有一定的了解;

  • 你能够对 nginx 与 php-fpm 之间的通信有个整体概念;

名词解释

Web 应用(Web Application)

一般指 PHP、Java 等编程语言开发的应用程序,包括网站或移动应用等。

服务器(Web Server)

一般指的是 Nginx、Apache、Lightpd、Tomcat 等。

Web 服务器只是内容的分发者。比如 Nginx,如果客户端请求了 /index.html,那么 Nginx 会去文件系统中找到这个文件,发送给浏览器,这里分发的是静态数据;如果客户端现在请求的是 /index.php,根据配置文件,Nginx 知道这个不是静态文件,需要去找 PHP 解析器来处理,那么它会把这个请求经过简单处理后交给 PHP 解析器。

CGI(Common Gateway Interface,通用网关接口)

CGI(Common Gateway Interface),通用网关接口。

它不是一门编程语言,而是一种协议,是 Web 服务器运行外部程序时的规范

这种外部程序称为 CGI 程序,也就是实现了 CGI 接口标准的程序,只要编程语言具有标准输入、标准输出,就可以用来编写 CGI 程序。

每种动态语言(PHP、Python 等)的代码文件需要通过对应的解析器解析后,才能被服务器所识别。而 CGI 协议就是用来使解析器与服务器可以互相通信,文件在服务器上的解析需要用到 PHP 解析器,再加上对应的 CGI 协议,从而使服务器可以解析 PHP 文件。

通俗的解释就是,web 服务器需要和外部程序进行交互,那么它们得知道双方想要些什么,比如 Nginx 会传输哪些数据给 PHP 解析器呢?URL 要有吧,查询字符串也得有吧,POST 数据也要有,HTTP 请求头不能少吧,CGI 就是规定了要传哪些数据,以什么样的格式传递给后方处理这个请求的一个协议。

CGI 模式

缺点

CGI 程序使得外部程序与 web 服务器的交互成为可能,但是由于 CGI 运行模式,导致其存在着很大的问题。

CGI 模式的运行原理是:当 Nginx 收到浏览器 /index.php 这个请求后,首先会创建一个对应实现了 CGI 协议的进程,这里就是 PHP-CGI(PHP 解析器)。接下来 PHP-CGI 会解析 php.ini 文件,初始化执行环境,然后处理请求,再以 CGI 规定的格式返回处理后的结果,退出进程。最后,Nginx 再把结果返回给浏览器,处理完后再销毁进程。整个流程就是一个 Fork-And-Execute 模式。

所以 php-cgi 接收到 CGI 传来的数据后,执行流程为:环境初始化 -> 处理请求 -> 返回结果 -> 杀死进程。每个请求都将对应一个 php-cgi 进程。

可以看到,如果有成千上万个请求,那么就会重复成千上万次重复的创建和销毁进程操作。这种方式实现虽然容易,但是效率是非常差的。而且由于是独立进程,地址空间无法共享,也限制了资源的重用,比如每次进程创建都会解析 php.ini 文件,当 php.ini 文件没有发生改变时,这个操作其实是没必要的。

基于这些问题,后面推出了 FastCGI 协议,也是 CGI 协议的一个改进和优化。

优点

CGI 模式的优点是完全独立于任何服务器,仅仅是做为一个中介:提供接口给 Web 服务器和脚本语言,它们通过 CGI 协议搭线来完成数据传输,这样做的好处是尽量减少它们之间的关联,使得各自更加独立、互不影响。

FastCGI(Fast Common Gateway Interface)

FastCGI 即 Fast Common Gateway Interface,快速通用网关接口,可以理解为是 CGI 协议的改进版本。

FastCGI 致力于减少网页 Web 服务器和 CGI 程序之间交互的开销,从而使得服务器可以同时处理更多的 Web 请求。

那么 FastCGI 是如何做到这点的呢?

FastCGI 的设计模式是:一个进程管理器(Master 进程)和多个工作进程(Worker 进程)。

FastCGI 模式运行原理如下:

  1. 启动 FastCGI 进程管理器;

  2. 解析 php.ini 文件,初始化执行环境;

  3. 启动多个 CGI 协议解释器守护进程,并等待来自 Web 服务器的连接;

  4. 当客户端请求到达 Web 服务器时,FastCGI 进程管理器会选择并连接到一个 CGI 解释器,Web 服务器将 CGI 环境变量和标准输入发送到 FastCGI 的子进程 PHP-CGI 中,PHP-CGI 子进程完成处理后便将标准数据和错误信息返回给 Web 服务器;此时 PHP-CGI 子进程就会关闭连接,该请求便处理结束,接着等待并处理来自 FastCGI 进程管理器的下一个请求连接。

FastCGI 的执行流程为:启动 FastCGI 进程管理器 -> 初始化执行环境 -> 启动多个 Worker 进程 -> Master 进程接收请求,将请求分配给 Worker 进程,Worker 进程处理请求。

优点

  1. 性能好,通过设置管理进程和工作进程,解决了 CGI 频繁创建进程、修改进程的问题。相比于 CGI,它更像是一个常驻性的 CGI 程序,即它将 CGI 解释器进程一直保持在内存中运行。它可以一直运行着,不会每次收到 web 请求都去创建新的进程处理然后再销毁,从而获得较高的性能。

  2. 稳定性高,FastCGI 模式是以独立的进程池来运行 CGI 协议程序,单独一个进程死掉,系统可以很轻易的丢弃,然后重新分配新的进程来运行逻辑。

PHP-CGI

PHP-CGI 是 PHP(web application)对 Web 服务器提供的 CGI 协议的接口程序。PHP-CGI 早期是 PHP 实现的自带的 FastCGI 管理器,虽然那时是官方自带的,但是性能却很差。

在 php5.3 以前,是采用 PHP-CGI 来实现 FastCGI Web 请求。从 php5.4 开始, PHP-FPM 取代了 PHP-CGI,即负责进程管理。

使用


php-cgi -b 127.0.0.1:9000

缺点

  • 每一次 PHP-CGI 程序处理请求前,都需要加载 php.ini 文件、加载所有的扩展模块等许多重复的工作;

  • php.ini 文件每次发生变更,就需要重启 PHP-CGI 程序才能让新的 php.ini 生效,不可以平滑启动;

  • 直接杀死 PHP-CGI 程序,PHP 就不能运行了(PHP-FPM 则没有这个问题,守护进程会平滑的启动新的子进程)

PHP-CGI 只是个 CGI 程序,本身只会处理解析请求,并返回结果,不会管理进程。所以之后就初恋了一些能够调度 PHP-CGI 进程的程序,这就是后面 PHP-FPM 的出现原因。

PHP-FPM

PHP-FPM 是 PHP FastCGI Process Manager 的缩写,是 FastCGI 进程管理器。

PHP-FPM 是基于 master/worker 的多进程架构模式,与 nginx 的设计风格类似。master 进程主要负责 CGI、PHP 环境初始化,事件监听、子进程状态,worker 进程负责处理 PHP 请求。

FPM 的 master 通过共享内存获取 worker 进程的信息,包括 worker 进程当前状态、已处理请求数等,当 master 进程要杀掉一个 worker 进程时则通过发送信号的方式通知 worker 进程。

FPM 的实现首先是创建一个 master 进程,在 master 进程中创建并监听 socket,然后 fork 出多个子进程,这些子进程各自 accept 请求。子进程的处理很简单,它在启动后阻塞在 accept 上,有请求到达后开始读取请求数据,读取完后开始处理然后再返回,在这期间不会接收其它请求。它跟 nginx 的事件驱动模型是不同的,nginx 是非阻塞的模型,如果一个请求数据还未发送完则会处理下一个请求,也就是说一个进程会同时连接多个请求。

优点

  • 提供了更好的 PHP 进程管理方式,支持平滑停止/启动进程;

Nginx 与 PHP-FPM 交互过程

以常见的 LNMP 架构为例,Nginx 在一台服务器上,PHP 在另一台服务器上。

当我们请求一个域名的时候,比如一个测试域名,www.test.com,域名解析到 Nginx 服务器,Nginx 路由到 index.php 文件。此时 Nginx 检测出这不是静态文件,需要找 PHP 解析器来解析,然后加载 Nginx 的 FAST_CGI 模块,并 fastcgi_pass 到 PHP 服务器,比如 10.20.0.1:9000。此时 PHP 服务器上的 PHP-FPM 子进程监听到了请求,然后处理请求。其流程如下:


浏览器访问 www.test.com

|

|

域名解析到 Nginx 服务器

|

|

路由到 www.test.com/index.php

|

|

Nginx 检测 index.php 不是静态资源,加载 Nginx 的 fast-cgi 模块

|

|

请求被转发到 PHP 所在的服务器上

|

|

PHP 服务器上的 fast-cgi 监听 127.0.0.1:9000 地址

|

|

www.test.com/index.php 请求到达 127.0.0.1:9000

|

|

PHP-FPM worker 进程执行代码

Nginx 与 PHP-FPM 通信方式

上面我们已经介绍了 Nginx 与 PHP-FPM 的交互方式。下面我们再来看看 Nginx 和 PHP-FPM 是如何通信的。

在 Linux 上,Nginx 和 PHP-FPM 通信有两种方式,tcp-socket 和 unix-socket。

当 Nginx 和 PHP-FPM 不在同一台机器上时,只能使用 tcp-socket 这种通信方式。

两种通信方式的过程如下图所示(借用网上的一张图):

image.png

Unix 域套接字

在 Linux 系统中,有很多进程间通信方式,套接字(Socket)就是其中的一种。但是传统的套接字的用法都是基于 TCP/IP 协议栈的,需要指定 IP 地址。如果不同主机上的两个进程进行通信,当然这样做没什么问题。但是,如果只需要在一台机器上的两个不同进程间通信,还要用到 IP 地址就有点大材小用了。

对于套接字来说,还存在一种叫做 Unix 域套接字的类别。专门用来解决这个问题。

域套接字(Unix Domain Socket)又叫 IPC(inter-process communication 进程间通信)socket,简称 UDS,是基于 socket 的基础上发展而来的,用于实现同一主机上的进程间通信。socket 原本适用于不同机器上进程间的通讯,当然也可以用于同一机器上不同进程的通讯(通过 localhost),后来在此基础上,发展出专门用于同一机器进程间通讯的 IPC 机制,UDS 与原来的网络 socket 相比,不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC 机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。

tcp socket 和 unix socket 对比

  • 效率:理论上,Unix domain socket 不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序列号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。所以其效率比 tcp socket 的方式要高,可减少不必要的 tcp 开销。

  • 跨机器通信:tcp socket 支持跨机器通信,而 unix domain socket 是同一机器进程之间通信。

  • 高并发:实际上,在高并发情况下,两者的性能差距并不明显。但是 tcp socket 能表现出很明显的更高的稳定性

Nginx 配置


# tcp socket 通信方式

# PHP-FPM 监听的 IP 地址和端口,如果是本机的话,配置为: 127.0.0.1:9000

fastcgi_pass 127.0.0.1:9000;

# unix socket 通信方式

# 很多教程在 socket 方式时使用路径 /tmp,而路径 /dev/shm 是个 tmpfs(临时文件系统,驻留在内存中),速度比磁盘快得多

# fasrcgi_pass /dev/shm/php-fpm.sock

PHP-FPM 配置


listen = 127.0.0.1:9000

# 或者下面这样

listen = /dev/shm/php-fpm.sock

注意,在使用 unix socket 方式通信时,由于 socket 文件本质上是一个文件,存在权限控制的问题,所以需要注意 Nginx 进程的权限与 PHP-FPM 的权限,不然会提示无权限访问。(在各自的配置文件里设置用户)

总结

  1. CGI 是什么?
  • CGI 是一种协议,定义了 Web 服务器执行外部程序的规范,包括 Web 服务器与外部程序之间传递的数据格式、数据内容等。
  1. FASTCGI 与 CGI 的区别?
  • FASTCGI 是 CGI 的改进版本,CGI 的 Fork-And-Execute 模式,在高并发情况下,频繁的创建、销毁进程,效率非常低,FASTCGI 通过设置管理进程和工作进程,解决了 CGI 频繁创建进程、修改进程的问题。
  1. PHP-FPM 和 Nginx 通信机制有哪些?
  • tcp socket 和 unix socket
  1. tcp socket 和 unix socket 的区别是什么?
  • 效率:理论上,由于 unix socket 是进程间通信,所以效率会更高些。

  • 机器间通信:tcp socket 支持机器间通信,而 unix socket 不支持机器间通信。

  • 高并发:实际上,通过测试,在高并发情况下,tcp socket 通信方式更加稳定。

  1. 推荐使用哪种方式?
  • 推荐使用 tcp socket,有如下原因:
  1. 支持跨机器通信;

  2. 高并发业务更可靠;

  3. 如果 Nginx 要做负载均衡的话,根本不需要考虑 unix socket 的方式了,只能采用 tcp socket 的方式。

参考文档