本文目录导读:

- 基础环境准备(所有方案的前提)
- 最推荐方案:PHP-FPM + Nginx 平滑重载
- 进阶方案:符号链接(Symlink)原子切换
- 数据库迁移(最难的部分)
- 会话(Session)与缓存
- 多服务器/负载均衡方案(如果有多台机器)
- 高级混合架构(微服务/API)
- 针对 PHP 的特殊细节:OPcache
- 推荐实施路线
在PHP项目中实现零停机发布(Zero-Downtime Deployment)需要综合考虑代码部署、数据库迁移、缓存和会话管理等多个方面。
以下是几种常见的策略和最佳实践,按推荐程度和复杂度排序:
基础环境准备(所有方案的前提)
在深入策略之前,你需要先具备以下环境条件,否则零停机无从谈起:
- 使用版本控制(Git):确保任何变更都可回滚。
- 自动化构建工具:使用 GitLab CI、Jenkins、GitHub Actions 等。
- 前置检查:每次部署前自动跑单元测试和静态分析(如 PHPStan)。
最推荐方案:PHP-FPM + Nginx 平滑重载
这是最简单且成本最低的零停机方案,适用于大多数单体 PHP 应用(如 Laravel、Symfony)。
核心原理:PHP-FPM 支持 reload 操作,它不会立刻杀死旧进程,而是等当前请求处理完毕后,再启动新的 Worker 进程。
实现步骤:
- 拉取代码:在服务器上执行
git pull或者将新代码解压到新目录(推荐新目录,方便回滚)。 - 更新依赖:运行
composer install --no-dev --optimize-autoloader。 - 维护模式(可选但重要):
- 这一步不是让用户看到 503,而是只处理请求队列。
- PHP 代码是即时解析的,只要文件替换完成,下次请求就是新代码。
- 关键操作——重载 FPM:
- 执行
kill -USR2 $(cat /var/run/php-fpm.pid)或systemctl reload php-fpm。 - 注意:
systemctl reload比restart好,因为reload是平滑的(等待当前请求完成)。
- 执行
- 清理缓存:执行
php artisan config:cache和php artisan route:cache(如果代码有变更)。
优缺点:
- 优点:操作简单,不需要额外的硬件或网关。
- 缺点:如果代码文件是直接覆盖的,在覆盖瞬间(毫秒级)可能会有一段短暂的用户请求执行到“半旧半新”的代码(新类文件引用了已被删除的旧文件),为了彻底避免这个问题,需要引入符号链接(见下文)。
进阶方案:符号链接(Symlink)原子切换
这是目前最标准的生产级方案(类似 Capistrano 或 Deployer 的实现方式)。
核心原理:保留两个(或多个)代码目录(release_1 和 release_2),通过切换 current 软链接指向哪个目录来决定运行哪套代码,切换操作是原子性的(瞬间完成)。
目录结构:
/var/www/project/ ├── releases/ │ ├── 20240521_1200/ # 旧版本 │ └── 20240522_1400/ # 新版本 └── current -> releases/20240522_1400 # 软链接
Nginx 配置:
root /var/www/project/current/public;
部署流程:
- 将新代码上传到新的时间戳目录(如
releases/20240522_1400)。 - 在新目录里执行
composer install和 数据库迁移(如果兼容)。 - 修改软链接:
ln -sfn /var/www/project/releases/20240522_1400 /var/www/project/current。 - 最重要:执行
kill -USR2重载 PHP-FPM,清除 OPcache。- OPcache:需要配置
opcache.validate_timestamps=0(生产环境推荐),并在切换软链接后执行opcache_reset()或重载 FPM,否则 PHP 还是会跑旧代码(因为路径变了,但只要配置了realpath_cache,建议直接 reload)。
- OPcache:需要配置
- 回滚只需将软链接指回上一版本即可,秒级完成。
数据库迁移(最难的部分)
代码可以零停机,但数据库架构变更(如新增表、修改字段)往往会导致旧代码报错。
策略:
- 向后兼容原则(三步走):
- 新增:先添加新的字段/表,不删除旧字段,新旧代码都可用。
- 切换:发布新代码,使用新字段;此时旧代码还在运行(如果有),可能会报错,所以要确保旧代码忽略新字段。
- 清理:部署稳定后,再执行一个单独的脚本删除旧字段。
- 使用工具:使用
Phinx或 Laravel 的migrations。- 关键:不要在发布代码的同时执行
drop或rename操作。
- 关键:不要在发布代码的同时执行
- 大表操作:如果表很大(超过几万行),直接
ALTER TABLE会锁表,建议使用pt-online-schema-change(Percona Toolkit)来进行在线 DDL。
会话(Session)与缓存
- Session 存储:绝对不要将 Session 存到本地文件(
session.save_path指向本机),因为发布过程中,如果有多台服务器或容器被替换,用户 Session 会丢失。必须将 Session 存储到 Redis 或数据库。 - 业务缓存:
- 使用 Redis 或 Memcached。
- 在发布新代码时,如果缓存键名没有变,可能会导致新代码读到旧数据,建议设计缓存键时带上版本号(如
user_123_v2),或者在发布时执行cache:clear,但在高并发下,清空缓存可能导致缓存雪崩,需要谨慎,最好使用“渐进式更新”。
多服务器/负载均衡方案(如果有多台机器)
如果项目由多台机器跑,可以采取 滚动更新(Rolling Update):
- 摘除:将服务器 A 从负载均衡器(Nginx/HAProxy)中摘除(健康检查标记为 Down)。
- 更新:在服务器 A 上执行上述的部署步骤(代码+迁移)。
- 测试:在服务器 A 本地 curl 测试。
- 接入:将服务器 A 重新接入负载均衡。
- 重复:对服务器 B、C 重复以上步骤。
注意:数据库迁移必须在第一台机器更新前执行。
高级混合架构(微服务/API)
如果你的项目采用了前后端分离(PHP 仅作 API 服务):
- 版本化 API:不要在原有 API 上修改逻辑,直接新建
/api/v2/,让前端(SPA)先发版对接 v2,等旧客户端(App)全部更新后,再下架 v1。 - 消息队列:将耗时操作(如发送邮件、生成报表)放入 Redis 队列,发布期间,Worker 进程可以延迟重启,不中断数据流。
针对 PHP 的特殊细节:OPcache
这是最容易踩坑的地方,PHP 是解释型语言,如果不注意 OPcache,部署后会跑旧代码。
- 正确配置(
php.ini或 Docker 环境):
opcache.enable=1 opcache.validate_timestamps=0 # 生产环境设为 0 opcache.revalidate_freq=0
- 部署动作:在切换软链接(或覆盖代码)后,需要强制刷新 OPcache。
- 推荐:调用
php -r "opcache_reset();"或重载 FPM(kill -USR2),如果不刷新,可能需要等很久才生效。
- 推荐:调用
推荐实施路线
- 第一步:环境配置化(
.env),Session 入 Redis,开启 OPcache(关闭时间戳验证)。 - 第二步:如果是单机,使用 符号链接 + FPM Reload 方案(可以用 Deployer 工具辅助)。
- 第三步:数据库迁移采取“向后兼容”策略,利用工具平滑 DDL。
- 第四步:如果预算充足,引入 Docker Swarm 或 Kubernetes,利用其
Rolling Updates机制。
工具推荐:强烈建议使用 Deployer(PHP 生态专属部署工具),它原生支持符号链接、原子切换和 PHP-FPM 平滑重启,可以帮你自动处理上述 80% 的流程。