一句话

PHP 生命周期 = MINIT → RINIT → 执行 → RSHUTDOWN → MSHUTDOWN。
CLI/CGI 模式每个请求跑完整 5 步;FPM 模式只跑中间 3 步(M 阶段进程启动时跑一次);Swoole/Workerman 更进一步,只跑 1 步

五大阶段

阶段触发时机典型工作
MINIT (Module Init)进程启动加载扩展、注册类/函数/常量
RINIT (Request Init)每个请求开始初始化 $_GET/$_POST/$_SESSION、扩展请求级状态
ExecuteRINIT 之后把 PHP 源码编译成 opcode 并执行
RSHUTDOWN请求结束调注册的 shutdown 函数、清理临时变量
MSHUTDOWN进程退出卸载扩展、释放永久内存

CLI vs FPM vs Swoole

CLI(每次都完整 5 步)

1
php script.php
  • 启动:MINIT → RINIT → Execute → RSHUTDOWN → MSHUTDOWN → 退出
  • —— 每跑一次都要重新加载扩展、解析 INI、编译 opcode

FPM(M 步只跑一次)

1
2
3
4
5
[启动]   MINIT
[请求1] RINIT → Execute → RSHUTDOWN
[请求2] RINIT → Execute → RSHUTDOWN
...
[退出] MSHUTDOWN
  • worker 进程常驻,处理 N 个请求才退出(pm.max_requests
  • 配合 OPcache,opcode 缓存,编译只发生一次

Swoole / Workerman(M+R 都只跑一次)

1
2
3
4
[启动]   MINIT → RINIT → 业务初始化($app = new Application())
[请求1] -> handle($req)
[请求2] -> handle($req)
...
  • 全程在内存里,没有 RINIT/RSHUTDOWN 开销
  • 框架(Hyperf / EasySwoole)启动时就把容器、路由、ORM 全部初始化好
  • 代价:得自己处理状态污染、内存泄漏、协程上下文

一个请求里发生的事(FPM)

  1. Nginx 接到请求,通过 fastcgi 协议把请求转给 PHP-FPM master
  2. FPM master 派给空闲 worker(或新建)
  3. worker 跑 RINIT:填 $_SERVER $_GET $_POST,扩展级 hook
  4. worker 编译 PHP 文件 → opcode(OPcache 命中则跳过)
  5. 执行 opcode,业务代码跑起来
  6. 输出 buffer → fastcgi 回 Nginx
  7. RSHUTDOWN:执行注册的 register_shutdown_function、释放变量
  8. worker 回到空闲,等下一个请求;处理够 max_requests 后退出

性能影响最大的几点

优化点提升倍数说明
OPcache2-5x没有它每个请求都重新编译,直接砍 60% 性能
opcache.validate_timestamps=0(生产)+10-20%不再 stat 文件检查改动
FPM + 长连接 PDO+30%比 CLI 模式快得多
切到 Swoole / RoadRunner5-10x跳过 R 阶段,常驻内存
预加载(PHP 7.4+ preload)+5-10%MINIT 时就编译好核心类

SAPI 是什么

SAPI (Server API) = PHP 与外部环境对接的抽象层。CLI、FPM、Apache mod_php、Swoole 都各有自己的 SAPI。

1
2
php -r 'echo php_sapi_name();'
# cli

写扩展或框架时,常需要 if (php_sapi_name() === 'cli') 判断当前模式。

调试小技巧

1
2
3
4
// 把 RSHUTDOWN 阶段的执行打印出来
register_shutdown_function(function () {
echo "请求结束,内存峰值:" . memory_get_peak_usage(true) . "\n";
});

参考