Yii框架不支持PHP层面热更新,因请求级生命周期导致旧worker持旧字节码;需通过symlink切换目录+调优opcache(revalidate_freq=0)+PHP-FPM平滑reload实现无停机部署。

Yii 框架本身不支持真正的“热更新”(即运行中替换类文件并立即生效),所谓“无停机部署”在 Yii 中实际是通过目录切换 + 进程/服务控制实现的平滑过渡,不是 PHP 层面的代码热加载。
为什么直接改代码无法热更新
PHP 是请求级生命周期模型:每个 HTTP 请求启动新进程(或复用 FPM worker),加载一次全部类文件。修改 controllers/SiteController.php 后,旧 worker 仍持有旧字节码,新请求才可能加载新版本——但存在缓存、opcache、APCu 等干扰,导致行为不可控。
常见误操作和后果:
- 直接覆盖
frontend/web/index.php或config/main.php:可能引发白屏或 500 错误,因配置解析失败或类未定义 - 未清空
runtime/cache/和runtime/compile/:旧路由规则、视图编译结果残留,导致 URL 404 或模板渲染错乱 - 忽略 opcache 配置:即使文件已更新,
opcache.revalidate_freq=60会让 PHP 每分钟才检查一次文件变更,造成最长 60 秒延迟
用 symlink 切换发布目录(推荐方案)
核心思路:把 Web 根目录(如 Nginx 的 root)指向一个符号链接,每次部署生成新版本目录,再原子化切换链接目标。
操作步骤:
- 约定部署结构:
/var/www/myapp/releases/20260419120000/(含完整frontend/、backend/、vendor/)、/var/www/myapp/current → /var/www/myapp/releases/20260419120000 - 部署脚本中执行:
ln -nfs /var/www/myapp/releases/20260419120000 /var/www/myapp/current,该命令是原子操作,无中间态 - Nginx 配置始终指向
/var/www/myapp/current/frontend/web,不随版本变化 - 旧版本目录保留 2–3 个,便于快速回滚:
rm -rf /var/www/myapp/releases/20260418100000
注意:确保 runtime/ 和 web/assets/ 是跨版本共享的(用绝对路径软链或挂载卷),否则会丢失 session、日志、前端资源缓存。
配合 FPM 平滑重启(避免请求中断)
单纯切 symlink 不够——正在处理的请求仍跑在旧代码上,但新请求会走新版本。若需彻底“无感知”,需让 PHP-FPM worker 自然退出旧进程,不接受新请求。
关键配置与操作:
- 在
www.conf中启用优雅退出:pm.graceful_timeout = 30,确保 worker 在收到 SIGTERM 后最多再处理 30 秒请求 - 部署完成后执行:
sudo systemctl reload php*-fpm(或kill -USR2 $(cat /var/run/php/php*-fpm.pid)),触发平滑 reload - 验证方式:
ps aux | grep 'php-fpm: pool www' | grep -v grep,应看到新旧 worker 并存数秒,之后旧进程自动退出 - 不要用
restart,它会强制 kill 所有 worker,造成请求丢弃
opcache 必须调优才能配合部署
默认 opcache 会缓存脚本哈希,即使文件被替换也不重新校验,这是热更新失败最隐蔽的原因。
必须调整的 php.ini 项:
-
opcache.validate_timestamps=1(生产环境可设为 0,但部署时必须为 1) -
opcache.revalidate_freq=0(设为 0 表示每次请求都检查文件修改时间,部署后立即生效) -
opcache.enable_cli=1(保证php yii migrate等命令也能读到最新代码) - 部署后可选执行:
opcache_reset()(放在临时维护脚本里),但更推荐靠revalidate_freq=0自动解决
复杂点在于:opcache 配置是全局生效的,不能 per-app 控制;若多项目共用 PHP-FPM,需统一协调策略。这点容易被忽略,也是线上出现“明明改了代码却没生效”的首要排查方向。


















