PHP 运行模式
web 服务器
一般情况下,服务器使用 Nginx、Apache、IIS 等作为 Http 服务端,处理客户端的连接,充当客户端与服务端应用程序的中间层;
应用程序可以是 PHP、Python、NodeJs等,当一个 HTTP 请求发送给服务器程序(如 Nginx),服务器程序会根据配置文件寻找到对应的应用程序(如PHP),并将 HTTP 请求数据交给应用程序,应用程序处理完毕后将返回数据交给服务器程序,服务器程序发送给终端用户。
PHP 的运行模式与 http 服务器软件密切相关,主要由服务器软件以及服务器软件加载 php 的方式决定,可以看 php 文档中的 这个 页面,php_sapi_name
有如此多的可能性,这代表 php 的运行模式细分有很多,但总结下来,笼统的区分,可归纳为两种模式,非常驻 和 常驻进程 两种,这里就简单的,针对常见环境做个总结
CGI
全称为:Common Gateway Interface,通用网关接口
这是最容易跨平台实现,也是最容易理解的一种方法。应用程序接受到数据后,将数据丢给 PHP-CGI 接口,接口启动 PHP 并加载相关的代码,处理完毕后返回数据,并退出进程。
IIS,Apache 都支持这种方法,不过 Nginx 不支持,貌似 Ngxin 从一开始就没打算支持,因为这种方式虽然简单,但却带来了性能底下的弊端。
原因也较为容易理解,每次请求都会创建新的进程加载 PHP 内核 和 PHP 网站代码,用完了之后就直接仍了。每次有请求都会把这个活从头干一遍,低效。如果是开发人员自己调试还无所谓,但对外提供服务就不行了。
另外就是这种方式只是提供一种类似转发的功能,无法承受大的并发请求,想一想,如果有成千上万个请求,会创建成千上万的进程,服务器可能直接歇菜了。
但这种网关接口的模式是通用的,下面要说的 FastCGI 也仍旧是使用 CGI 的原理,但对运行过程进行了优化,用以应对网络应用。
FastCGI
全称为:Fast Common Gateway Interface,? 快速通用网关接口,也常写作 FCGI
这是一个较为通用的接口,也很容易跨平台。是现在使用最为广泛的网关接口,其原理可以说与 CGI 完全相同。
不同之处在于,FCGI 模式下,应用程序处理完请求之后不会立即退出,并且服务器程序明确知道都有那些应用程序的进程或线程仍然驻留在系统中, 当有新的请求进来时,服务器程序会进程调度,分配给空闲的应用程序处理。
不同服务器程序的实现过程可能略有不同,有些是在一开始便创建固定数目的进程,有些是根据请求数逐渐创建。 对于空闲时间,也可能略有不同,有些服务器程序会回收空闲进程,而有些则不会。
不过这些对于应用程序都是透明的,不需理会,应用程序只需要处理好:处理请求的初始化、处理之后的资源释放即可。
FastCGI 通常情况下是与服务器软件相互独立的,PHP 作为常驻进程对服务器软件提供处理数据的能力;
有些是需要单独的去管理 php 进程,如 php-fpm, 是可以单独的启动、停止的;
但有一些是交给服务器软件同进退的,由服务器软件自行加载 PHP , php 可能直接运行在服务器软件的进程里面,如 IIS 的 ISAPI、apache 通过 libphp 运行的 apache2handler;严格来说,这样的方式已不能称为 fastcgi 了,但我们这里按照是否常驻来区分,所以也笼统的放到 fastcgi 中。
线程安全
PHP win 版本提供了 线程安全、非线程安全两种版本。首先说结论:CGI模式运行选择非线程安全;ISAPI、FCGI选择线程安全。
windows 下的 ISAPI 和 FCGI 守护运行的都是 PHP 线程,这些线程会公用一些资源,为了避免不同线程之间的数据错乱,需要选择线程安全版本。 而对于 CGI 模式,本来就是每次都创建先的线程,所以也就无所谓了,选择非线程安全反而可以提升性能。
Linux 下没有区别,首先 Linux 下没有 ISAPI,而 FCGI 模式,PHP 是以守护进程在运行,也不存在多进程公用内存的情况,所以就没有了,再说了,php 只有 win 才提供两种版本的下载 :)
FastCGI 的礼物
由于是守护模式在运行,所以可以做一些资源常驻。但仅限于语言层面,就是说 PHP 内核提供的才可以使用,而自己开发的 PHP 具体应用是不可以的。 因为守护进程在每次处理完 PHP 请求后会释放 PHP 具体应用的资源。
PHP 提供了一些利用常驻进程的功能,如 PHP PDO 可以支持数据库长连接,守护进程创建数据库连接后不释放,下次处理请求时直接利用。
一个 php 进程会 handle 一个连接通道,并不是所有进程公用通道,所以当你使用 pdo 长连接时,一台服务器打开的连接数是由 php 进程数决定的。
CLI:更进一步
PHP 内核倒是利用了 FCGI 的好处,但自己编写的代码也想利用怎么办呢?
其实只有一个选项:干掉服务器程序,通过 PHP-CLI 自己完全接手请求处理。
事实上,JAVA、NodeJs、Python 等都已经这么做了,这种方式之所以一直没在 PHP 中流行起来。 是因为这种模式下要完全自己控制内存,容易内存泄漏,即使今天,各种常驻程序也不乏内存泄漏的程序,有些还是很知名的开源框架。
可能是在互联网疯狂发展的几年中,PHP 承包了绝大多数的生产场景,大家为了快速开发,快速上线。 干脆直接使用 CGI,这样反而来的稳当,来掩盖快速开发中产生的问题。
还有另外一个原因,PHP 的线程调度,上下文切换的相关接口不是十分友好。若无法利用好这个功能,也不容易开发出比 FCGI 更强劲的系统。
WorkerMan 的出现缓解了这个问题,使用纯 PHP 编写,对这部分接口进行了二次封装,掩盖了大部分细节,对外提供更为友好的接口。 让更加快速的开发 CLI 运行的系统变得更加简单。
Swoole 使用 c++ 从底层重新实现了进程管理,甚至还支持了协程调度,也让 CLI 系统的开发如虎添翼。
cli 问题一
内存,在使用 CLI 时候切记要注意内存管理,避免泄露。
cli 问题二
直接使用 PHP CLI 对外提供服务的话,,需要自行处理很多细节。比如 HTTP 协议,SSL 验证等,如果自己处理,无疑是没事找事。
科学的方式是,PHP 以 CLI 运行,通过 Nginx 等服务器程序反向代理 PHP 运行端口,这样可以同时利用二者的优势。
cli 问题三
每次修改代码后,需要重新 reload 或 restart 才能生效,这个可以通过一些 watch 应用自动化,不过这也会带来额外的系统负担