根本原因是PHP-FPM平滑重启时旧进程被强制终止导致Nginx收到502,需配置pm.graceful_timeout、fastcgi_read_timeout并用USR2信号重启,配合TCP端口+upstream实现自动摘除,opcache失效问题需启用file_cache或预热。

ThinkPHP 应用重启时为什么用户会看到 502 或白屏
根本原因是 PHP-FPM 子进程在 reload 或 restart 时,正在处理的请求被强制中断,而 Nginx 在收不到 FastCGI 响应时直接返回 502 Bad Gateway;ThinkPHP 本身无热加载机制,opcache.revalidate_freq=2 这类配置也救不了正在执行中的请求。
关键不在 ThinkPHP,而在 PHP-FPM 和 Nginx 的协作节奏。平滑重启不是“让 TP 不报错”,而是确保:旧请求跑完、新请求不进旧进程、Nginx 不因连接断开甩锅。
- PHP-FPM 的
pm.graceful_timeout必须大于 ThinkPHP 最长可能响应时间(比如含导出、上传回调等场景),建议设为60 - Nginx 的
fastcgi_read_timeout要 ≥pm.graceful_timeout,否则它会先于 PHP-FPM 主动断连 - 避免用
systemctl restart php-fpm—— 改用kill -USR2 $(cat /var/run/php/php-fpm.pid)触发平滑 reload
如何让 Nginx 在 PHP-FPM 切换时不打到已停用的 worker
默认情况下,Nginx 会持续把请求发给 upstream 中所有 backend,哪怕某个 PHP-FPM pool 已进入 graceful shutdown 阶段但 socket 文件还存在。它不知道“这个进程快死了”,只认连接是否可写。
真正起作用的是 fastcgi_pass 后端的健康探测 + 主动摘除逻辑,但原生 Nginx 不支持对 unix socket 做 active health check。所以得换思路:
立即学习“PHP免费学习笔记(深入)”;
- 用
upstream+server指向 TCP 端口(如127.0.0.1:9000),而非unix:/run/php/php-fpm.sock,这样能配合max_fails=1 fail_timeout=10s实现自动踢出 - PHP-FPM 配置里为每个 pool 设置独立端口(如
listen = 127.0.0.1:9001),滚动更新时先启新 pool、再关旧 pool,Nginx 自动切流 - 不要依赖
fastcgi_next_upstream error timeout http_502来兜底 —— 它只能重试,不能防止首请求失败
为什么 thinkphp:reload 命令不能替代 PHP-FPM 平滑重启
php think reload 只是触发了 ThinkPHP 内部的配置/路由缓存刷新,并不会重建运行时实例,更不会影响已 fork 出来的 PHP-FPM worker 进程。它对正在运行的请求完全透明,也解决不了 FPM 进程级的冷启动问题。
这个命令适合开发环境改了 config 后快速生效,但在生产负载均衡集群中,它和“平滑重启”毫无关系:
- 它不释放内存、不清理 opcache 共享内存段、不重载扩展模块
- 如果用了
Swoole或Workerman驱动,think reload才有意义;纯 FPM 模式下它只是个安慰剂 - 真正要更新代码,必须让 PHP-FPM worker 加载新文件 —— 这只能靠进程重启或 opcache 失效(但后者有并发 race condition)
PHP-FPM reload 期间 opcache 缓存失效的坑怎么绕
PHP-FPM reload 会清空所有 worker 的 opcache,导致刚 reload 完那几十个请求全部重新编译 PHP 文件,CPU 瞬间拉满,ThinkPHP 的 vendor/autoload.php 和框架核心文件尤其明显。
这不是 ThinkPHP 的问题,是 opcache 默认策略:进程重启即 cache 重置。缓解方法只有两个方向:
- 启用
opcache.validate_timestamps=0+ 定期opcache_reset()(需配合部署脚本,在新代码上线后、reload 前调用) - 用
opcache.file_cache(如/tmp/opcache),它能在进程间共享编译结果,reload 后新 worker 直接从磁盘加载,跳过 recompile - 别设
opcache.max_accelerated_files过小(ThinkPHP + Composer autoload 至少需要 20000+),否则命中率暴跌,file_cache 也救不了
最麻烦的点其实是:opcache 的 file_cache 路径必须由所有 PHP-FPM worker 进程可读写,且不能放在 tmpfs(如 /dev/shm)上——否则每次 reload 都丢失。



















