在搭建 LAMP/LNMP 服务器时, 会经常遇到 PHP-FPM,FastCGI 和 CGI 这几个概念. 如果对它们一知半解, 很难搭建出高性能的服务器. 接下来我们就以图形方式, 解释这些概念之间的关系.
基础
在整个网站架构中, web Server(如 Apache)只是内容的分发者. 举个栗子, 如果客户端请求的是 index.html, 那么 Web Server 会去文件系统中找到这个文件, 发送给浏览器, 这里分发的是静态数据.
如果请求的是 index.PHP, 根据配置文件, Web Server 知道这个不是静态文件, 需要去找 PHP 解析器来处理, 那么他会把这个请求简单处理, 然后交给 PHP 解析器.
当 Web Server 收到 index.PHP 这个请求后, 会启动对应的 CGI 程序, 这里就是 PHP 的解析器. 接下来 PHP 解析器会解析 PHP.INI 文件, 初始化执行环境, 然后处理请求, 再以规定 CGI 规定的格式返回处理后的结果, 退出进程, Web server 再把结果返回给浏览器. 这就是一个完整的动态 PHP Web 访问流程, 接下来再引出这些概念, 就好理解多了,
CGI: 是 Web Server 与 Web Application 之间数据交换的一种协议.
FastCGI: 同 CGI, 是一种通信协议, 但比 CGI 在效率上做了一些优化. 同样, SCGI 协议与 FastCGI 类似.
PHP-CGI: 是 PHP (Web Application)对 Web Server 提供的 CGI 协议的接口程序.
PHP-FPM: 是 PHP(Web Application)对 Web Server 提供的 FastCGI 协议的接口程序, 额外还提供了相对智能一些任务管理.
Web 中,
Web Server 一般指 Apache,Nginx,IIS,Lighttpd,Tomcat 等服务器,
Web Application 一般指 PHP,Java,ASP.NET 等应用程序.
Module 方式
在了解 CGI 之前, 我们先了解一下 Web server 传递数据的另外一种方法: PHP Module 加载方式. 以 Apache 为例, 在 PHP Module 方式中, 是不是在 Apache 的配置文件 httpd.conf 中加上这样几句:
- # 加入以下 2 句
- LoadModule php5_module D:/PHP/php5apache2_2.dll
- AddType application/x-httpd-PHP .PHP
- # 修改如下内容
- <IfModule dir_module>
DirectoryIndex index.PHP index.HTML
</IfModule>
上面是 Windows 下安装 PHP 和 apache 环境后手动配置, 在 Linux 下源码安装大致是这样配置的:
# ./configure --with-MySQL=/usr/local --with-apache=/usr/local/apache --enable-track-vars
所以, 这种方式, 他们的共同本质都是用 LoadModule 来加载 php5_module, 就是把 PHP 作为 apache 的一个子模块来运行. 当通过 Web 访问 PHP 文件时, apache 就会调用 php5_module 来解析 PHP 代码.
那么 php5_module 是怎么来将数据传给 PHP 解析器来解析 PHP 代码的呢? 答案是通过 sapi.
我们再来看一张图, 详细的说说 apache 与 PHP 与 sapi 的关系:
从上面图中, 我们看出了 sapi 就是这样的一个中间过程, SAPI 提供了一个和外部通信的接口, 有点类似于 socket, 使得 PHP 可以和其他应用进行交互数据(apache,nginx 等).PHP 默认提供了很多种 SAPI, 常见的提供给 apache 和 nginx 的 php5_module,CGI,FastCGI, 给 IIS 的 ISAPI, 以及 Shell 的 CLI.
所以, 以上的 apache 调用 PHP 执行的过程如下:
apache -> httpd -> php5_module -> sapi -> PHP
好了. apache 与 PHP 通过 php5_module 的方式就搞清楚了吧!
这种模式将 PHP 模块安装到 apache 中, 所以每一次 apache 结束请求, 都会产生一条进程, 这个进程就完整的包括 PHP 的各种运算计算等操作.
在上图中, 我们很清晰的可以看到, apache 每接收一个请求, 都会产生一个进程来连接 PHP 通过 sapi 来完成请求, 可想而知, 如果一旦用户过多, 并发数过多, 服务器就会承受不住了.
而且, 把 mod_php 编进 apache 时, 出问题时很难定位是 PHP 的问题还是 apache 的问题.
CGI
CGI(Common Gateway Interface)全称是 "通用网关接口",Web 服务器与 PHP 应用进行 "交谈" 的一种工具, 其程序须运行在网络服务器上. CGI 可以用任何一种语言编写, 只要这种语言具有标准输入, 输出和环境变量. 如 PHP,perl,tcl 等.
Web 服务器会传哪些数据给 PHP 解析器呢? URL, 查询字符串, POST 数据, HTTP header 都会有. 所以, CGI 就是规定要传哪些数据, 以什么样的格式传递给后方处理这个请求的协议. 仔细想想, 你在 PHP 代码中使用的用户从哪里来的.
也就是说, CGI 就是专门用来和 Web 服务器打交道的. Web 服务器收到用户请求, 就会把请求提交给 CGI 程序(如 PHP-CGI),CGI 程序根据请求提交的参数作应处理(解析 PHP), 然后输出标准的 HTML 语句, 返回给 Web 服服务器, Web 服务器再返回给客户端, 这就是普通 CGI 的工作原理.
CGI 的好处就是完全独立于任何服务器, 仅仅是做为中间分子. 提供接口给 apache 和 PHP. 他们通过 CGI 搭线来完成数据传递. 这样做的好处了尽量减少 2 个的关联, 使他们 2 变得更独立.
但是 CGI 有个蛋疼的地方, 就是每一次 Web 请求都会有启动和退出过程, 也就是最为人诟病的 fork-and-execute 模式, 这样一在大规模并发下, 就死翘翘了.
FastCGI 介绍
FastCGI 简单介绍
从根本上来说, FastCGI 是用来提高 CGI 程序性能的. 类似于 CGI,FastCGI 也可以说是一种协议.
FastCGI 像是一个常驻 (long-live) 型的 CGI, 它可以一直执行着, 只要激活后, 不会每次都要花费时间去 fork 一次. 它还支持分布式的运算, 即 FastCGI 程序可以在网站服务器以外的主机上执行, 并且接受来自其它网站服务器来的请求.
FastCGI 是语言无关的, 可伸缩架构的 CGI 开放扩展, 其主要行为是将 CGI 解释器进程保持在内存中, 并因此获得较高的性能. 众所周知, CGI 解释器的反复加载是 CGI 性能低下的主要原因, 如果 CGI 解释器保持在内存中, 并接受 FastCGI 进程管理器调度, 则可以提供良好的性能, 伸缩性, Fail- Over 特性等等.
FastCGI 的工作原理
FastCGI 接口方式采用 C/S 结构, 可以将 HTTP 服务器和脚本解析服务器分开, 同时在脚本解析服务器上启动一个或者多个脚本解析守护进程. 当 HTTP 服务器每次遇到动态程序时, 可以将其直接交付给 FastCGI 进程来执行, 然后将得到的结果返回给浏览器. 这种方式可以让 HTTP 服务器专一地处理静态请求, 或者将动态脚本服务器的结果返回给客户端, 这在很大程度上提高了整个应用系统的性能.
Web Server 启动时载入 FastCGI 进程管理器(Apache Module 或 IIS ISAPI 等)
FastCGI 进程管理器自身初始化, 启动多个 CGI 解释器进程(可建多个 PHP-CGI), 并等待来自 Web Server 的连接.
当客户端请求到达 Web Server 时, FastCGI 进程管理器选择并连接到一个 CGI 解释器. Web server 将 CGI 环境变量和标准输入发送到 FastCGI 子进程 PHP-CGI.
FastCGI 子进程完成处理后, 将标准输出和错误信息从同一连接返回 Web Server. 当 FastCGI 子进程关闭连接时, 请求便告处理完成. FastCGI 子进程接着等待, 并处理来自 FastCGI 进程管理器 (运行在 Web Server 中) 的下一个连接. 在 CGI 模式中, PHP-CGI 在此便退出了.
FastCGI 与 CGI 特点:
对于 CGI 来说, 每一个 Web 请求 PHP 都必须重新解析 PHP.INI, 重新载入全部扩展, 并重新初始化全部数据结构. 而使用 FastCGI, 所有这些都只在进程启动时发生一次. 一个额外的好处是, 持续数据库连接 (Persistent database connection) 可以工作.
由于 FastCGI 是多进程, 所以比 CGI 多线程消耗更多的服务器内存, PHP-CGI 解释器每进程消耗 7 至 25 兆内存, 将这个数字乘以 50 或 100 就是很大的内存数.
PHP-FPM 介绍
要了解 PHP-FPM, 就得先说说 PHP-CGI.
PHP-CGI 就是 PHP 实现的自带的 FastCGI 管理器. 虽然是 PHP 官方出品, 但是这丫的却一点也不给力, 性能太差, 而且也很麻烦不人性化, 主要体现在:
PHP-CGI 变更 PHP.INI 配置后, 需重启 PHP-CGI 才能让新的 PHP-INI 生效, 不可以平滑重启.
直接杀死 PHP-CGI 进程, PHP 就不能运行了.
上面 2 个问题, 一直让很多人病垢了很久, 所以很多人一直还是在用 Module 方式. 直到 2004 年一个叫 Andrei Nigmatulin 的屌丝发明了 PHP-FPM , 这神器的出现就彻底打破了这种局面, 这是一个 PHP 专用的 fastcgi 管理器, 它很爽的克服了上面 2 个问题, 而且, 还表现在其他方面更表现强劲.
也就是说, PHP-FPM 是对于 FastCGI 协议的具体实现, 他负责管理一个进程池, 来处理来自 Web 服务器的请求. 目前, PHP5.3 版本之后, PHP-FPM 是内置于 PHP 的.
因为 PHP-CGI 只是个 CGI 程序, 他自己本身只能解析请求, 返回结果, 不会进程管理. 所以就出现了一些能够调度 PHP-CGI 进程的程序, 比如说由 lighthttpd 分离出来的 spawn-fcgi. 同样, PHP-FPM 也是用于调度管理 PHP 解析器 PHP-CGI 的管理程序.
PHP-FPM 通过生成新的子进程可以实现 PHP.INI 修改后的平滑重启.
总结
最后, 我们来总结一下, 这些技术经过不断的升级, 可以解决什么问题(不然也不会升级嘛).
所以, 如果要搭建一个高性能的 PHP Web 服务器, 目前最佳的方式是 Apache/Nginx + FastCGI + PHP-FPM(+PHP-CGI)方式了, 不要再使用 Module 加载或者 CGI 方式啦:)
本文章图片用 Visio 制作, 源文件:
来源: http://www.mzh.ren/cgi_fastcgi_php-fpm.html