从CGI到FastCGI到PHP-FPM最详细的文章

背景

笔者在学习这几个名词的时候,也是被百度到的相关文章迷惑。涉及到的主要名词包括
CGI协议, CGI脚本, PHP-CGI , FastCGI协议, PHP-FPM
要真正理解这些名词,如果我们硬生生的解释,也很难记住。我们可以从web服务器开发的历程来看,看看他们为什么会出现,以及他们解决了什么问题。

早些年的简单服务器

早些年的服务器很简单,你访问一个网站,比如www.helloworld.com,网站只返回你一个静态HTML页面。为了简单起见,我们假设网站值返回hello标题,这样流程可以用下图表示
image
浏览器和服务器请求流程
这时候server是采用C语言编写的,为了说明问题,最简化一个服务器脚本。基本上一个简的C脚本就可以了,把这个服务器脚本命名为hello_server.c,代码可以到github上下载 在Linux环境中使用gcc编译hello_server.c
vagrant@kfz:~$ gcc hello_world.c
然后采用curl工具测试
vagrant@kfz:~$ curl "127.0.0.1:8887"GET / HTTP/1.1User-Agent: curl/7.49.1Host: 127.0.0.1:8887Accept: *
/*
Hello World
这里我们的重点不在HTTP响应的格式上,所以我们直接输出了Hello World,如果是请求静态页面,我们也是一样的思路,读取静态文件的内容然后返还给客户端。 思考:当Web时代越来越火爆,我们希望Web服务器有更多的功能,比如写博客,聊天,等等。同时,越来越多的不同领域的开发者想在web时代大显身手。很多服务器开发者发现有了以下缺点
#服务器功能方面
  Web服务器功能会随着这种逻辑的增长而增长,服务器会变的不专一
#语言支持方便
  越来越多的PHP,Python开发者想开发服务端程序,编写更多的功能,发现自己的语言无从下手

CGI协议与CGI程序

什么是CGI协议和CGI程序

既然Web服务器想做的专一,但又要支持Web的飞速发展,同时还想让其他语言开发者也能加入Web开发。所以,就出了一种方式,Web服务器和其他领域的开发者拟定一条协议,在保证遵守协议的基础上,剩下的可以自由发挥。没错,这条协议就是CGI协议,协议的内容可以查看,而实现这个协议的脚本叫做CGI程序。CGI协议规定了需要向CGI脚本设置的环境变量和一些其他信息,CGI程序完成某一个功能,可以用PHP,Python,Shell或者C语言编写。 这样的好处Web开发更加开放了,可以用任何开发者擅长的语言开发Web程序。比如,我是一个PHP开发者,用PHP有很多现成的库访问第三方服务,入MySQL,Memcache等,但是这些第三方服务使用C语言接入还是比较麻烦的。有了CGI协议,我们的Web处理流程可以变成下图这样
image

实现一个PHP版的CGI程序

一般情况下,我们把CGI程序放在cgi-bin目录下面,新建PHP版CGI处理程序cgi-bin/get_user.php,我们的服务器程序还是使用简单的hello_server.c目标:用户访问URL127.0.0.1:8887/cgi-bin/get_user.php?id=1的的时候输出用户ID为1的用户信息 实现:很显然,我们要解决两个问题
在服务器程序hello_server.c中执行get_user.php文件服务器程序hello_server.c向get_user.php中传递请求信息(id=1)
第一个问题我们使用C库还是popen来运行php脚本,第二个问题在CGI协议中规定了我们的解决方案。首先需要将请求参数放在环境变量中,其次,环境变量的名字也规定好了。这里列举一些CGI协议规定的常用的环境变量,这里只列举一些,全部的内容可以协议明细
变量名 描述
CONTENT_TYPE 这个环境变量的值指示所传递来的信息的MIME类型。目前,环境变量CONTENT_TYPE一般都是:application/x-www-form-urlencoded,他表示数据来自于HTML表单
HTTP_USER_AGENT 提供包含了版本数或其他专有数据的客户浏览器信息
QUERY_STRING 如果服务器与CGI程序信息的传递方式是GET,这个环境变量的值即使所传递的信息。这个信息经跟在CGI程序名的后面,两者中间用一个问号’?’分隔
REQUEST_METHOD 提供脚本被调用的方法。对于使用 HTTP/1.0 协议的脚本,仅 GET 和 POST 有意义
所以,关于服务端怎么向CGI程序传递请求信息的问题就可以解释了。在服务端接收到用户请求的时候需要按照CGI协议通过putenv设置环境变量,然后PHP程序也是按照CGI协议解析环境变量(PHP中使用$_SERVER即可)。 程序的实现,可以在这里下载 最后需要说明的,当服务器执行php cgi-bin/get_user.php的时候,实际上是使用php的命令行模式执行的,因为PHP已经为我们封装好了环境变量,所以我们只需要从$_SERVER中就可以获取我们需要的环境变量。这里只为了说明问题,实际上PHP的sapi里面有单独实现cgi接口。当然你也可以用shell实现,shell的话是直接获取环境变量

CGI程序的缺点

CGI工作原理:每当客户请求CGI的时候,WEB服务器就请求操作系统生成一个新的CGI解释器进程(如php-cgi),CGI进程则处理完一个请求后退出,下一个请求来时再创建新进程。我们知道,执行一个PHP程序的必须要先解析php.ini文件,然后模块初始化等等一系列工作,每次都反复这样非常浪费资源。当然,这样在访问量很少没有并发的情况也行。可是当访问量增大,并发存在,这种方式就不适合了。这也是CGI程序一个致命的缺点。

FastCGI协议和php-cgi

FastCGI协议

既然上面提到了CGI程序的一个致命缺陷,所以在CGI协议的基础上,又设计了FastCGI协议 ,协议的内容可以查看,概括一下,主要做了以下改变
FastCGI被设计用来支持常驻(long-lived)应用进程,减少了fork-and-execute带来的开销FastCGI进程通过监听的socket,收来自Web服务器的连接,这样FastCGI进程可以独立部署服务器和FastCGI监听的socket之间按照消息的形式发送环境变量和其他数据
所以,我们称实现了FastCGI协议的程序为FastCGI程序,在php中的sapi/cgi/php-cgi实现了这个协议,虽然它的名字取名为php-cgi,但其实是支持FacsCGI协议的。所以,实现了FastCGI协议的服务器和FastCGI程序的交互方式如下图所示
image
fastcgi进程工作图

PHP中FastCGI实现

可能很少人用过php-cgi,因为目前大家都使用php-fpm(先不讨论php-fpm)。这里介绍一下php-cgi的使用。 1.php-cgi如何启动监听socket php-cgi可执行程序为编译后的php目录下面的sapi/cgi/php-cgi。通过-b参数指定需要监听的ip和端口即可,比如我们想绑定本机的9004端口,如果是单进程模式,就可以直接使用这个socket了。
shell> ./php-cgi -b 127.0.0.1:9004
2.如何启动多个FastCGI进程 这是多进程模式启动FastCGI进程,和第一步不一样,php-cgi没有参数可以指定子进程数量,这里需要使用PHP_FCGI_CHILDREN环境变量表示派生多少个子进程,我们可以写一个shell来启动php-cgi,点击可以下载这个shell文件。注意替换成自己的cgi路径后再使用 3.通过shell脚本启动FastCGI管理器 执行start.sh脚本之后,可以查看到启动了多个FastCGI进程
shell> ps aux  | grep php-cgi | grep -v grepvagrant   6785  0.0  0.0   1936   524 pts/3    S    01:07   0:00 sh -c /home/vagrant/php-5.5.10/sapi/cgi/php-cgi -b 127.0.0.1:9004vagrant   6792  0.0  0.3  13760  3188 ?        Ss   01:07   0:00 /home/vagrant/php-5.5.10/sapi/cgi/php-cgi -b 127.0.0.1:9004vagrant   6793  0.0  0.1  13760  1384 ?        S    01:07   0:00 /home/vagrant/php-5.5.10/sapi/cgi/php-cgi -b 127.0.0.1:9004vagrant   6794  0.0  0.1  13760  1384 ?        S    01:07   0:00 /home/vagrant/php-5.5.10/sapi/cgi/php-cgi -b 127.0.0.1:9004vagrant   6795  0.0  0.1  13760  1384 ?        S    01:07   0:00 /home/vagrant/php-5.5.10/sapi/cgi/php-cgi -b 127.0.0.1:9004vagrant   6796  0.0  0.1  13760  1384 ?        S    01:07   0:00 /home/vagrant/php-5.5.10/sapi/cgi/php-cgi -b 127.0.0.1:9004vagrant   6797  0.0  0.1  13760  1384 ?        S    01:07   0:00 /home/vagrant/php-5.5.10/sapi/cgi/php-cgi -b 127.0.0.1:9004vagrant   6798  0.0  0.1  13760  1384 ?        S    01:07   0:00 /home/vagrant/php-5.5.10/sapi/cgi/php-cgi -b 127.0.0.1:9004vagrant   6799  0.0  0.1  13760  1384 ?        S    01:07   0:00 /home/vagrant/php-5.5.10/sapi/cgi/php-cgi -b 127.0.0.1:9004vagrant   6800  0.0  0.1  13760  1384 ?        S    01:07   0:00 /home/vagrant/php-5.5.10/sapi/cgi/php-cgi -b 127.0.0.1:9004
4.和Nginx配合测试 在Nginx配置文件中加上这一段
location ~ \.php$ {    fastcgi_pass   127.0.0.1:9004;    include        fastcgi.conf;}
重启nginx之后就可以测试nginx访问php了。

php-cgi的的缺点

php-cgi实现的已经很完美了,但是我们的需求总是有点苛刻,导致它暴露了一下问题 1. 当我们更改配置文件后,php-cgi显然无法平滑重启 2. 我们fork的进程个数和请求量正比,请求繁忙时,fork进程多,动态调整,显然php-cgi没做到

spawn-fcgi和PHP-FPM

既然上面提及php-cgi实现的FastCGI问题官方没有解决。那自然有第三方做这个事情,我们可以使用Lighttpd的spawn-fcgi来管理php-cgi进程。注意,这里的spawn-fcgi是进程管理器,只是管理实现FastCGI协议的php-cgi,这个的安装和使用方式可以参照鸟哥的博客 还有一个做的比较好的,php-fpm,以前php-fpm并没有纳入php安装包文件中,只是在它的卓越表现,得到了越来越多的人认可,后面就被纳入了官方默认安装包文件中。php-fpm是从5.3开始才进入PHP源代码主干的,可以独立运行,不依赖php-cgi,换句话说,他自己实现了FastCGI协议。 所以,spawn-fcgi和PHP-FPM不是同一个东西,前者是进程管理器,后者却实现FastCGI协议并且支持进程平滑重启并且带进程管理功能

« »

发表评论

电子邮件地址不会被公开。 必填项已用*标注

昵称 *