CGI、FastCGI、PHP-CGI与PHP-FPM

CGI

最早的Web服务器简单地响应浏览器发来的HTTP请求,并将存储在服务器上的HTML文件返回给浏览器,也就是静态HTML。随着对网站的需求日益复杂,一些语言开始能写出动态网站,但是服务器本身并不能直接解析这些文件,服务器自己不能做,只能交给第三方处理,但是要提前与第三方做好约定,我给你什么,然后你给我什么,就是我把请求参数发送给你,然后我接收你的处理结果,再返回给客户端。那这个约定就是 Common Gateway Interface,简称CGI。这个协议可以用PHPPython、C/C++来实现。如php-CGI,就是对CGI协议的实现。

这里写图片描述

服务器与CGI程序的交互步骤: 
WEB服务器将根据CGI程序的类型决定数据向CGI程序的传送方式,一般来讲是通过标准输入/标准输出流环境变量来与CGI程序间进行数据的传递。 如下图所示:

这里写图片描述

CGI程序通过标准输入(STDIN)标准输出(STDOUT)来进行输入输出数据。此外 CGI程序还通过环境变量来得到输入,操作系统提供了许多环境变量,它们定义了程序的执行环境,应用程序可以读取它们。Web服务器和CGI接口又另外设置了一些环境变量,用来向CGI程序传递一些重要的参数。

一些常用的CGI环境变量如:CONTENT_TYPE、CONTENT_LENGTH、HTTP_COOKIE、QUERY_STRING、REMOTE_HOST等,在PHP脚本中,都可以通过 $_SERVER[‘xxx’]来获取。这些CGI环境变量都是服务器程序通过 setenv() 设置的。

简单总结,整个服务器接收请求到响应请求有如下几步:

1、通过Internet把用户请求送到服务器。 
2、服务器接收用户请求并转交给CGI程序处理,同时传递一些变量。
3、CGI程序把处理结果传送给服务器。 
4、服务器把结果送回到用户。

这里写图片描述

CGI方式在遇到连接请求(用户请求)先要创建CGI的子进程,然后该进程请求,处理完后结束这个子进程。这就是令人诟病的fork-and-execute模式。

所以用CGI方式的服务器有多少连接请求就会有多少CGI进程,进程反复加载初始化,每次都要做些大量重复的操作是CGI性能低下的主要原因。都会当用户请求数量非常多时,会大量挤占系统的资源如内存,CPU时间等,造成性能低下。

比如CGI模式下运行的PHP,每次启动CGI进程启动都要初始化 Zend引擎和核心组件、重新载入全部扩展、解析php.ini等等一系列的操作,这就造成了资源的严重浪费。

FastCGI

为了解决CGI性能低下的问题,FastCGI就出现了。

FastCGI像是一个常驻(long-live)型的CGI,实现了FastCGI协议的程序,作为一个CGI的管理服务器存在,会预先启动一系列的子进程来等待处理,然后等待 web服务器发过来的请求,一旦接受到请求就交由子进程处理,由于不需要等接受到请求后再启动CGI,会快很多。

FastCGI是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI进程保持在内存中并因此获得较高的性能。CGI程序的反复加载是CGI性能低下的主要原因,如果CGI程序保持在内存中并接受FastCGI进程管理器调度,则可以提供良好的性能、伸缩性、Fail- Over特性等等。

FastCGI特点

  • FastCGI具有语言无关性.

  • FastCGI在进程中的应用程序,独立于核心web服务器运行,提供了一个比API更安全的环境。APIs把应用程序的代码与核心的web服务器链接在一起,这意味着在一个错误的API的应用程序可能会损坏其他应用程序或核心服务器。 恶意的API的应用程序代码甚至可以窃取另一个应用程序或核心服务器的密钥。

  • FastCGI技术目前支持语言有:PHP、C/C++、Java、Perl、Tcl、Python、SmallTalk、Ruby等。相关模块在Apache, ISS, Lighttpd等流行的服务器上也是可用的。

  • FastCGI本身不依赖于任何Web服务器的内部架构,因此即使服务器技术的变化, FastCGI依然稳定不变。

FastCGI的工作流程

这里写图片描述

1、FastCGI进程管理器自身初始化,启动多个CGI程序(可见多个php-cgi)并等待来自Web Server的连接。
2、Web服务器与FastCGI进程管理器进行Socket通信,通过 FastCGI协议发送CGI环境变量和标准输入给CGI程序。
3、CGI程序完成处理后将标准输出从同一TCP连接返回 Web 服务器。
4、CGI程序接着等待并处理来自Web服务器的下一个请求。

FastCGI与传统CGI模式的区别之一则是Web服务器不是直接执行 CGI程序了,而是通过Socket与FastCGI 进程管理器进行交互,也正是因为FastCGI进程管理器是基于Socket通信的,所以也是分布式的,Web 服务器可以和响应器服务器分开部署。Web服务器需要将数据 CGI/1.1 的规范封装在遵循 FastCGI 协议包中发送给 FastCGI进程管理器。

使用FastCGI,许多繁杂的工作都只在php-cgi进程启动时发生一次。一个额外的好处是,持续数据库连接可以工作,并且同一个进程中的请求都可以使用这个连接。

PHP-FPM

PHP-FPM是PHP对FastCGI 的一种具体实现,它的启动后自行读取php.ini和php-fpm.conf,并创建多个 CGI子进程,然后主进程负责管理子进程,同时它对外提供一个SOCKET, Web服务器要转发一个请求时只需要按照FastCGI协议要求的格式将数据发往这个SOCKET就行了,PHP-FPM创建的这些子进程会去争抢这个 SOCKET连接,谁抢到了谁就处理该请求并将结果返回给Web服务器。

它作为一个独立的进程存在,通过SOCKET与Nginx建立连接。它和Apache + module_php有相像之处。

PHP-FPM采用多进程模式来实现并发: 
这里写图片描述

每个进程里串行地执行每个请求(使用poll处理请求): 
这里写图片描述

一个master进程,支持多个pool,每个pool由master进程监听不同的端口,pool中有多个worker进程。

每个worker进程支持在运行时编译脚本并在内存中缓存生成的 Opcode来提升性能。

可以通过pm.max_requests配置来指定,每个worker进程响应多少请求后自动重启。(重启是为了解决偶尔发生的内存泄露问题)

每个worker进程支持保持一个到MySQL/Memcached/Redis的持久连接,实现”连接池”,避免一个进程中的多个请求重复地建立连接,对程序透明。

master进程并不接收和分发请求,而是worker进程直接accpet请求后poll处理。

如果worker进程不够用,master进程会prefork更多进程。

如果prefork达到了pm.max_children上限,worker进程又全都繁忙,这时master进程会把请求挂起到连接队列backlog里(默认值是511)。

在PHP-FPM中,PHP的所有变量都在请求时创建,请求结束后全部销毁,也就是每个请求之间都是独立的,这虽然避免了内存泄露,但是对于某些大数组、对象、常量,重复地创建非常浪费资源(即使使用了 APC/OpCache,也只是节省了编译Opcode的时间,但是Opcode依然要执行并且花费时间)。若想实现将这些常驻内存,可以考虑使用Swoole扩展来作Server,替代Nginx+PHP-FPM

从网络中转载~