仓库源文站点原文

PHP 各种运行方式的不完整总结

TS 和 NTS

TS(Thread-Safety) 即线程安全,多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时进行数据加锁保护,其他线程不能同时进行访问该数据,直到该线程读取完毕,其他线程才可访问使用该数据,好处是不会出现数据不一致或者数据污染的情况,但耗费的时间要比 NTS 长。

NTS(None-Thread Safe) 即非线程安全,不提供数据访问保护,有可能出现多个线程先后或同时操作同一数据的情况,容易造成数据错乱(即脏数据),一般操作的执行时间要比 TS 短。

可以用这样的命令来区分当前的 php 是 TS 还是 NTS

php -i | grep "Thread Safety"
php -r "echo PHP_ZTS ? 'ZTS' : 'NTS';"

通常只在 windows 环境会区分 TS 和 NTS 。 linux 环境下通常只有 NTS 。 源码编译时手动指定 --enable-maintainer-zts 才会启用 TS TS 的线程锁机制在 Linux 多进程模型下是冗余开销,实测性能比 NTS 低 5%~15%。

PHP 常用的 SAPI

SAPI (Server Application Programming Interface,服务端应用编程端口)

可以这样查看 php 当前的运行模式

echo php_sapi_name();

和 http 服务器的组合

php 自身提供 http 服务

  1. 在 cli 模式下实现一个 http 服务,例如 swoole 或 workerman
  2. php 的内置服务器

这时 php 可以直接提供 http 服务或者通过反向代理和其它 http 服务器组合使用。

php 的内置服务器是单进程单线程的,运行的效率并不高

cgi 模式

通过 php-cgi 和 http 服务器的组合,通常是 php-cgi + apache , nginx 不能直接支持 cgi 。 但现在已经很少这用部署了,因为 cgi 的性能并不好。 其实 cli 也能实现 cgi 的,但是要自行解释 标准输入 和 环境变量 里的参数,然后自行构造 http 的响应头。

fastcgi 模式

  1. 直接使用 php-cgi 对接 http 服务器,例如 windows 环境下的 nginx + php-cgi ,好像是因为 fpm 依赖 fork ,但 windows 里没有 fork ,所以 fpm 没有 windows 版
  2. 使用 fpm 对接 http 服务器,例如 linux 环境下的 nginx + fpm
  3. 使用一个 fastcgi 管理器,然后配置 php-cgi 对接 http 服务器,例如 nginx + spawn-fcgi + php-cgi

使用 php-cgi 时最好配置这个环境变量 PHP_FCGI_CHILDREN 。

PHP_FCGI_CHILDREN 默认值是 1 ,大致就是一个管理器,一个子进程,速度估计也就比 php 的内置服务器好一点。

如果 PHP_FCGI_CHILDREN 的值为 0 ,就只有一个子进程,那么只要累计的请求数达到 PHP_FCGI_MAX_REQUESTS , php-cgi 就会自动退出,继续有请求时 nginx 就会返回 502 。

php-cgi 没有平滑重启,修改 php.ini 后要重启 php-cgi 。

apache 模块

<!-- apache 的 模块 mod_fcgid 用于没有 fastcgi 管理进程的情况 例如 PHP_FCGI_CHILDREN=0 php-cgi PHP_FCGI_CHILDREN保持默认值也是可以运行的,只是会浪费一点内存,毕竟会多出一个管理进程 FcgidWrapper 指令包装 环境变量也可以使用 FcgidInitialEnv 设置,但它们将应用于所有应用程序 PHP 包装脚本 - /usr/local/bin/php-wrapper #!/bin/sh # 设置所需的 PHP_FCGI_* 环境变量。 # 示例 # PHP FastCGI 进程默认在处理 500 个请求后退出。 PHP_FCGI_MAX_REQUESTS=10000 export PHP_FCGI_MAX_REQUESTS PHP_FCGI_CHILDREN=0 export PHP_FCGI_CHILDREN # 用您启用了 FastCGI 的 PHP 可执行文件的路径替换 exec /usr/local/bin/php-cgi mod_fastcgi mod_fastcgi 是第三方模块,也是最先出现支持 fastcgi 的模块 mod_fastcgi 已经很久没更新了 mod_fastcgi 可以支持 没有 fastcgi 管理进程的情况 也支持 有 fastcgi 管理进程的情况 https://github.com/FastCGI-Archives/mod_fastcgi https://fastcgi-archives.github.io/mod_fastcgi.html 可以简单但不严谨地理解为旧版的 mod_fcgid 这个模块是来自 Open Market mod_proxy_fcgi 用有 fastcgi 管理进程的情况 例如 fpm PHP_FCGI_CHILDREN=1 php-cgi spawn-fcgi + php-cgi fcgistarter + php-cgi apache 2.4 之后就有的 fcgi 工具 mod_cgid mod_cgi -->

apache 模块的运行方式也是十分流行的运行方式。

这里需要注意的是,要根据 apache 的 mpm (Multi-Processing Module) 来选择 PHP 的版本。

mpm 的比较常用模式有四种。

PHP 官方不建议用线程化 MPM (比如 event ) 的 Apache 来跑 PHP https://www.php.net/manual/zh/faq.installation.php#faq.installation.apache2

总结

php 的运行模式有很多种。 现在 php 比较流行的部署方式,就只有两种

因为 swoole 的发展,在 cli 下实现 http 服务的运行方式也越来越流行了。

无论哪种运行方式,本质上都是 接收数据 处理数据 输出数据 三板斧。

性能最好的做法依然是 nginx + fpm

apache 以 prefork 运行时并不支持 http/2 。 如果一定要用 apache , 同时 apache 前面没有其它的网关, 使用 PHP-FPM + proxy_fcgi + event MPM + mod_http2 ,这才是现代、高性能的组合。