PHP项目零停机发布如何实现

wen PHP项目 3

本文目录导读:

PHP项目零停机发布如何实现

  1. 基础环境准备(所有方案的前提)
  2. 最推荐方案:PHP-FPM + Nginx 平滑重载
  3. 进阶方案:符号链接(Symlink)原子切换
  4. 数据库迁移(最难的部分)
  5. 会话(Session)与缓存
  6. 多服务器/负载均衡方案(如果有多台机器)
  7. 高级混合架构(微服务/API)
  8. 针对 PHP 的特殊细节:OPcache
  9. 推荐实施路线

在PHP项目中实现零停机发布(Zero-Downtime Deployment)需要综合考虑代码部署数据库迁移缓存会话管理等多个方面。

以下是几种常见的策略和最佳实践,按推荐程度复杂度排序:

基础环境准备(所有方案的前提)

在深入策略之前,你需要先具备以下环境条件,否则零停机无从谈起:

  • 使用版本控制(Git):确保任何变更都可回滚。
  • 自动化构建工具:使用 GitLab CI、Jenkins、GitHub Actions 等。
  • 前置检查:每次部署前自动跑单元测试和静态分析(如 PHPStan)。

最推荐方案:PHP-FPM + Nginx 平滑重载

这是最简单成本最低的零停机方案,适用于大多数单体 PHP 应用(如 Laravel、Symfony)。

核心原理:PHP-FPM 支持 reload 操作,它不会立刻杀死旧进程,而是等当前请求处理完毕后,再启动新的 Worker 进程。

实现步骤

  1. 拉取代码:在服务器上执行 git pull 或者将新代码解压到新目录(推荐新目录,方便回滚)。
  2. 更新依赖:运行 composer install --no-dev --optimize-autoloader
  3. 维护模式(可选但重要)
    • 这一步不是让用户看到 503,而是只处理请求队列
    • PHP 代码是即时解析的,只要文件替换完成,下次请求就是新代码。
  4. 关键操作——重载 FPM
    • 执行 kill -USR2 $(cat /var/run/php-fpm.pid)systemctl reload php-fpm
    • 注意systemctl reloadrestart 好,因为 reload 是平滑的(等待当前请求完成)。
  5. 清理缓存:执行 php artisan config:cachephp artisan route:cache(如果代码有变更)。

优缺点

  • 优点:操作简单,不需要额外的硬件或网关。
  • 缺点:如果代码文件是直接覆盖的,在覆盖瞬间(毫秒级)可能会有一段短暂的用户请求执行到“半旧半新”的代码(新类文件引用了已被删除的旧文件),为了彻底避免这个问题,需要引入符号链接(见下文)。

进阶方案:符号链接(Symlink)原子切换

这是目前最标准的生产级方案(类似 Capistrano 或 Deployer 的实现方式)。

核心原理:保留两个(或多个)代码目录(release_1release_2),通过切换 current 软链接指向哪个目录来决定运行哪套代码,切换操作是原子性的(瞬间完成)。

目录结构

/var/www/project/
├── releases/
│   ├── 20240521_1200/   # 旧版本
│   └── 20240522_1400/   # 新版本
└── current -> releases/20240522_1400  # 软链接

Nginx 配置

root /var/www/project/current/public;

部署流程

  1. 将新代码上传到新的时间戳目录(如 releases/20240522_1400)。
  2. 新目录里执行 composer install 和 数据库迁移(如果兼容)。
  3. 修改软链接:ln -sfn /var/www/project/releases/20240522_1400 /var/www/project/current
  4. 最重要:执行 kill -USR2 重载 PHP-FPM,清除 OPcache。
    • OPcache:需要配置 opcache.validate_timestamps=0(生产环境推荐),并在切换软链接后执行 opcache_reset() 或重载 FPM,否则 PHP 还是会跑旧代码(因为路径变了,但只要配置了 realpath_cache,建议直接 reload)。
  5. 回滚只需将软链接指回上一版本即可,秒级完成。

数据库迁移(最难的部分)

代码可以零停机,但数据库架构变更(如新增表、修改字段)往往会导致旧代码报错。

策略

  • 向后兼容原则(三步走):
    1. 新增:先添加新的字段/表,不删除旧字段,新旧代码都可用。
    2. 切换:发布新代码,使用新字段;此时旧代码还在运行(如果有),可能会报错,所以要确保旧代码忽略新字段。
    3. 清理:部署稳定后,再执行一个单独的脚本删除旧字段。
  • 使用工具:使用 Phinx 或 Laravel 的 migrations
    • 关键:不要在发布代码的同时执行 droprename 操作。
  • 大表操作:如果表很大(超过几万行),直接 ALTER TABLE 会锁表,建议使用 pt-online-schema-change(Percona Toolkit)来进行在线 DDL。

会话(Session)与缓存

  • Session 存储绝对不要将 Session 存到本地文件(session.save_path 指向本机),因为发布过程中,如果有多台服务器或容器被替换,用户 Session 会丢失。必须将 Session 存储到 Redis 或数据库。
  • 业务缓存
    • 使用 RedisMemcached
    • 在发布新代码时,如果缓存键名没有变,可能会导致新代码读到旧数据,建议设计缓存键时带上版本号(如 user_123_v2),或者在发布时执行 cache:clear,但在高并发下,清空缓存可能导致缓存雪崩,需要谨慎,最好使用“渐进式更新”。

多服务器/负载均衡方案(如果有多台机器)

如果项目由多台机器跑,可以采取 滚动更新(Rolling Update)

  1. 摘除:将服务器 A 从负载均衡器(Nginx/HAProxy)中摘除(健康检查标记为 Down)。
  2. 更新:在服务器 A 上执行上述的部署步骤(代码+迁移)。
  3. 测试:在服务器 A 本地 curl 测试。
  4. 接入:将服务器 A 重新接入负载均衡。
  5. 重复:对服务器 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),如果不刷新,可能需要等很久才生效。

推荐实施路线

  1. 第一步:环境配置化(.env),Session 入 Redis,开启 OPcache(关闭时间戳验证)。
  2. 第二步:如果是单机,使用 符号链接 + FPM Reload 方案(可以用 Deployer 工具辅助)。
  3. 第三步:数据库迁移采取“向后兼容”策略,利用工具平滑 DDL。
  4. 第四步:如果预算充足,引入 Docker Swarm 或 Kubernetes,利用其 Rolling Updates 机制。

工具推荐:强烈建议使用 Deployer(PHP 生态专属部署工具),它原生支持符号链接、原子切换和 PHP-FPM 平滑重启,可以帮你自动处理上述 80% 的流程。

抱歉,评论功能暂时关闭!