PHP 怎么滚动更新

wen PHP项目 2

PHP 应用滚动更新实战指南:从零实现零停机发布

目录导读

  1. 为什么需要滚动更新?——传统部署的痛点
  2. 滚动更新的核心原理与架构设计
  3. PHP 滚动更新的五种主流实现方案
    • 基于负载均衡器的优雅摘除
    • Docker + Kubernetes 原生滚动策略
    • PHP-FPM 平滑重启与预加载
    • 符号链接 + 原子切换(经典 Laravel/ThinkPHP 方式)
    • Git 钩子 + 队列任务解耦
  4. 数据库与缓存的一致性处理(重点)
  5. 回滚机制与监控告警
  6. 常见问题 FAQ(滚动更新失败怎么办?)

为什么需要滚动更新?——传统部署的痛点

传统 PHP 部署通常执行 git pull 或直接覆盖代码文件,当流量高峰时,使用 FTP 覆盖文件会导致用户访问到“半新半旧”的代码,抛出致命错误,更糟的是,PHP 没有像 Node.js 那样的热重载机制,每次更新都需要重启 PHP-FPM,而这会中断所有正在处理的请求。

PHP 怎么滚动更新

滚动更新(Rolling Update)的核心目标:不停止服务,分批次替换旧版本实例,使整个更新过程中始终有可用节点承接流量


滚动更新的核心原理与架构设计

滚动更新的标准流程:

旧版本实例(v1) → 逐个/分批启动新实例(v2)
→ 健康检查通过后,从负载均衡中摘除旧实例
→ 重复直至所有实例替换完成

关键设计点:

  • 无状态化:PHP 应用不应在本地文件系统保存 session 或数据(需用 Redis 等外部存储)。
  • 共享存储:上传文件、日志必须挂在 NFS 或对象存储上。
  • 版本隔离:新老代码同时运行在一台机器上(通过端口或路径区分),但对外只暴露一个入口。

PHP 滚动更新的五种主流实现方案

基于负载均衡器的优雅摘除(最通用)

工具:Nginx + upstream + 动态脚本

步骤:

  1. 在 Nginx 配置中定义两个 upstream 池(v1 和 v2),指向不同端口(如 9001/9002)。
  2. 发布时,先启动 v2 代码的 PHP-FPM(监听 9002 端口)。
  3. 修改 Nginx upstream 权重,将 10% 流量切到 v2,验证无异常后逐步调整为 100%。
  4. 最后销毁 v1 进程。
upstream php_backend {
    server 127.0.0.1:9001 weight=0;  # 旧版本
    server 127.0.0.1:9002 weight=10; # 新版本
}

nginx -s reload 实现无缝切换。

Docker + Kubernetes 原生滚动策略(云原生)

K8s 天然支持 strategy: RollingUpdate,只需配置:

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxUnavailable: 1
    maxSurge: 1

配合 readinessProbe(检测 PHP 接口返回 200)和 livenessProbe(防止死锁),K8s 会逐个替换 Pod,直到新 Pod 全部健康。

PHP-FPM 平滑重启与预加载(轻量级)

对于单机部署,PHP 8.1+ 支持 opcache.preload,可在不中断请求下重新编译类,配合 systemctl reload php-fpm(非 restart),该命令会等待当前请求处理完再结束 worker。

进阶技巧:

# 先复制新代码到临时目录
cp -r /app /app_v2
# 修改 /app 软链接 指向 /app_v2
ln -sfn /app_v2 /app
# 触发 php-fpm 优雅重启
kill -USR2 $(cat /run/php-fpm.pid)

符号链接 + 原子切换(最常用)

Laravel、ThinkPHP 项目发布常这么做:

# 目录结构
/releases/v1.0.0
/releases/v2.0.0
/current -> /releases/v2.0.0   # 软链接
# 发布流程
git clone /releases/v2.0.0
cd /releases/v2.0.0 && composer install
ln -sfn /releases/v2.0.0 /current
# 执行数据库迁移(在切换后异步执行)

注意:确保 PHP 的 realpath_cache 设置为 0 或使用 opcache.validate_timestamps=0,否则旧路径缓存会导致找不到新文件。

Git 钩子 + 队列任务解耦

高级用法:利用 Git 的 post-receive 钩子,将耗时操作(如缓存预热、数据迁移)放入消息队列(Redis / RabbitMQ),Web 进程立即切换代码,而队列消费者异步处理旧数据。


数据库与缓存的一致性处理(重点)

滚动更新最大的坑是 数据库结构变更 导致新旧代码不兼容。

  • 兼容式迁移:永远不要删除字段或表,只新增字段(允许 NULL 或带默认值)。
  • 双写阶段:新代码写入新字段时,主动同步到旧字段,以便回滚。
  • 缓存清空策略:不要全局 flushall,而是采用版本化 key 前缀(如 cache_v2_),切换后逐个淘汰。

示例:

// 新增字段 user_avatar_style,老代码不识别
// 迁移脚本:ALTER TABLE users ADD COLUMN avatar_style VARCHAR(10) DEFAULT 'default';
// 新代码读取该字段,老代码忽略,不会报错。

回滚机制与监控告警

回滚方案

  • 软链接回滚只需一秒:ln -sfn /releases/v1.0.0 /current
  • Docker/K8s 回滚:kubectl rollout undo deployment/php-app

监控指标

  • 错误率(通过日志系统如 Sentry)
  • 请求延迟 P95/P99
  • PHP-FPM 连接数(pm.status_path

建议开启 金丝雀发布(Canary):先让 5% 的用户进入新版本,观察 10 分钟,无异常再全量。


常见问题 FAQ

Q1:滚动更新时,正在处理的请求会中断吗? 不会,只要用 graceful reload(如 kill -USR2),PHP-FPM 会等待当前脚本执行完毕,但新的请求会被拒绝,配合负载均衡摘除节点,可以实现零中断。

Q2:代码更新了,但 opcache 缓存了旧代码怎么办? 方案一:opcache_reset() 在 CLI 中执行。 方案二:在 php.ini 中设置 opcache.validate_timestamps=1(默认)且 opcache.revalidate_freq=0,这样每次请求都会检查文件修改时间(性能略降)。 推荐做法:部署脚本里执行 php -r "opcache_reset();" 清理对应 FPM 池的缓存(需用与 FPM 相同的用户)。

Q3:共享存储(如上传文件)如何保证新老版本都能访问? 将 upload 目录软链到外部 NFS 或 S3,不要在代码目录内。ln -s /data/uploads /app/current/public/uploads

Q4:如果新代码有 bug,如何快速识别? 通过日志中打印 APP_VERSION 环境变量标记,或使用 $_ENV['RELEASE_TAG'] 区分流量,若错误率超过阈值,立即回滚。

Q5:没有 Docker 环境,最小实现滚动更新的成本要多少? 仅靠 Nginx + PHP-FPM 多实例 + 软链接即可,实现成本极低,适合中小项目,核心思想是:让同一时刻只有一个版本的代码在承担所有流量


滚动更新不是某一项具体技术,而是一套围绕“原子切换 + 优雅退出 + 灰度放量”的发布哲学,对于 PHP 开发者而言,最简单的入手路径是:符号链接切换 配合 PHP-FPM reload,再逐步进阶到 K8s,在实施过程中,务必把数据库兼容性缓存版本化放在首位,才能做到真正的零停机发布。

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