PHP项目发布期间如何保证服务不中断

wen PHP项目 29

本文目录导读:

PHP项目发布期间如何保证服务不中断

  1. 方案一:负载均衡 + 蓝绿部署(最推荐、最成熟)
  2. 方案二:滚动更新(基于容器化)
  3. 方案三:利用 PHP-FPM 的平滑重载(较简单,但有局限)
  4. 方案四:基于 Git 的分支部署 + 预发布 Hook(适合极简场景)
  5. 数据库迁移:零停机部署的最大挑战
  6. 最佳实践清单

在 PHP 项目发布期间,保证服务不中断(即实现零停机部署平滑发布)的核心思路是:让旧版本继续服务,直到新版本完全就绪并能接管流量

由于 PHP 通常是无状态、每次请求重新加载的语言(除非使用 Swoole/Workerman 等常驻内存模式),其部署策略与 Java/Node.js 有所不同,但同样可以实现零停机。

以下是几种常用的、行之有效的方案,按推荐程度和复杂度排序:


负载均衡 + 蓝绿部署(最推荐、最成熟)

这是最标准、最可靠的零停机方案,适用于大多数 Web 应用。

原理: 准备两组完全独立的应用服务器集群,一组是“蓝色”(当前生产环境),一组是“绿色”(新版本环境),通过负载均衡器(Nginx、HAProxy、云服务商 SLB)瞬间切换流量。

实现步骤:

  1. 环境准备:

    • 蓝组: 当前正在运行的生产环境,对外提供服务。
    • 绿组: 与蓝组完全相同的配置(操作系统、Web 服务器、PHP 版本、扩展、数据库连接等),但代码是新版本。绿组在部署期间不对外服务
  2. 数据库兼容:

    • 关键点: 新代码必须向下兼容旧代码,即:数据库的变更(增删字段、表)必须保证蓝组代码依然能正常运行。
    • 实践: 采用“先加后删”策略,先给表添加一个新字段(新字段允许为空或默认值),部署绿组代码使用该字段,待蓝绿切换完成,旧代码下线后,再移除不需要的旧字段。
  3. 部署流程:

    • 在绿组上部署新版本 PHP 代码。
    • 运行数据库迁移脚本(必须是向前兼容的)。
    • 在绿组上运行全面的冒烟测试或自动化测试。
    • 切换: 修改负载均衡器配置,将流量从蓝组全部切换到绿组(修改 Nginx upstream 权重为 100:0 或直接更换服务器 IP),切换瞬间完成。
    • 观察: 监控绿组报错(5xx错误率、慢查询、CPU负载),如果出现问题,一键回滚:将负载均衡器切回蓝组。

优点:

  • 切换速度极快,对用户几乎无感知。
  • 回滚极其简单、快捷(切回旧组即可)。
  • 绿组可以承载“灰度流量”或压测流量。

缺点:

  • 资源成本翻倍(需要两倍的服务器)。
  • 数据库兼容性设计需要额外注意。

Nginx 配置示例:

upstream app_blue {
    server 192.168.1.10:80 weight=100;
    server 192.168.1.11:80 weight=100;
}
upstream app_green {
    server 192.168.1.20:80 weight=100;
    server 192.168.1.21:80 weight=100;
}
server {
    listen 80;
    server_name yourdomain.com;
    location / {
        # 默认指向蓝色
        proxy_pass http://app_blue;
    }
}

部署时,将 proxy_pass 指向 app_greennginx -s reload


滚动更新(基于容器化)

适用于使用 Docker/Kubernetes 的场景,这是容器编排平台(K8s、Docker Swarm)的原生能力。

原理: 逐步用新版本 Pod 替换旧版本 Pod,每次替换一个或一批,确保始终保持一定数量的 Pod 在服务中。

实现步骤(以 K8s 为例):

  1. 构建新镜像: 将新代码打包成 Docker 镜像,打上版本标签(如 v2.0.1)。
  2. 更新 Deployment: 修改 Deployment 的镜像版本,K8s 会自动执行 RollingUpdate 策略。
  3. 过程:
    • K8s 先启动一个或多个新 Pod(包含新代码)。
    • 等待新 Pod 通过 readinessProbe(就绪探针)检测,确认可以接受流量。
    • Service 负载均衡器自动将流量分发到新 Pod。
    • K8s 优雅地(发送 SIGTERM 信号,让 PHP-FPM 处理完当前请求)停止一个或多个旧 Pod。
    • 重复此过程,直到所有旧 Pod 被替换。

关键配置:

  • strategy.type: RollingUpdate
  • maxSurge:更新期间可以超过期望副本数的最大 Pod 数(通常设为 25%)。
  • maxUnavailable:更新期间允许不可用的最大 Pod 数(通常设为 0 或 25%)。
  • readinessProbe必须仔细配置,用 httpGet 请求你的 PHP 应用的 /health 端点,只有当新版本代码健康时才加入流量。

优点:

  • 资源利用率高,不需要额外服务集群。
  • 自动化程度高,K8s 原生支持。
  • 回滚方便(kubectl rollout undo)。

缺点:

  • 对 Docker/K8s 运维能力有要求。
  • 更新过程中,新旧版本会短暂共存,需要代码兼容性。
  • 数据库变更仍需谨慎处理。

利用 PHP-FPM 的平滑重载(较简单,但有局限)

适用于单服务器或小型应用。

原理: PHP-FPM 支持 reload 信号,它会:

  1. 读取新配置文件。
  2. 启动新的 Worker 进程组,使用新代码。
  3. 优雅地等待旧 Worker 进程处理完当前请求后,再将其终止。

实现方式:

  1. 代码部署: 先将新代码上传到服务器的临时目录(/var/www/app_v2/)。

  2. 软链接切换: 修改一个软链接(ln -sfn /var/www/app_v2 /var/www/current),让 Web 服务器(如 Nginx)的 rootdocument_root 指向新代码。

    这步本身是原子的,瞬间完成。

  3. 清除 Opcache: 这是 PHP 必须额外做的一步!

    • 因为 PHP 默认会缓存 Opcode,即使文件切换了,PHP 可能还在使用旧文件的 Opcode。
    • 解决方法: 在软链接切换后,立即给 PHP-FPM 发送 USR2 信号(或使用 opcache_reset() 函数),但更好的做法是:
      • 配置 opcache.validate_timestamps=1 并设置 revalidate_freq=2,但零停机效果依赖于 Opcache 在 2 秒后自动刷新,这有短暂不一致。
      • 最佳实践: 使用 php artisan optimize(Laravel)或手动调用 opcache_reset()(通过一个只有部署脚本能访问的 URL 或控制台命令)来一次性清除所有 Opcache
  4. 优雅重载: 执行 kill -USR2 <php-fpm-pid>,这会启动新 Worker,并优雅关闭旧 Worker。

优点:

  • 非常快。
  • 不需要额外硬件。

缺点:

  • 仅适用于无状态代码变更,如果数据库表结构有变化,这种方法无法避免新旧代码同时运行时出问题。
  • 重载期间 CPU 和内存会有短暂飙升。
  • 不适合大规模集群。

基于 Git 的分支部署 + 预发布 Hook(适合极简场景)

适用于个人项目或对一致性与并发要求不高的环境。不推荐用于高可用生产环境。

原理: 利用 Git 钩子(如 post-receive),在远程仓库收到新代码后,自动部署到生产服务器的另一个目录,然后通过软链接切换。

大致流程:

# 在服务器上 git push 接收后
git --work-tree=/home/www/app_v2 --git-dir=/home/repo/project.git checkout -f
# 运行 composer install (如果有)
# 运行数据库迁移 (先备份)
# 切换软链接
ln -sfn /home/www/app_v2 /home/www/current
# 清除 opcache(一定要做!)

限制:

  • 数据库不兼容问题依然存在。
  • 软链接切换不是零响应延迟的(可能有个别请求跑在旧代码上)。

数据库迁移:零停机部署的最大挑战

无论选择哪种部署方案,数据库变更是最容易导致中断的地方。

核心黄金法则: 所有数据库变更都必须向前兼容。

实践示例:

  • 重命名列: 不要用 ALTER TABLE users CHANGE old_name new_name ...,应该:

    1. 新增一个 new_name 列,类型与旧列相同,允许为空或有默认值。
    2. 部署新版本代码,写入 new_nameold_name,读取时优先或只读 new_name
    3. 运行一个修复脚本,将 old_name 的数据同步到 new_name
    4. 删除 old_name 列(在下次部署或独立维护窗口中进行)。
  • 删除列: 先部署新代码(不再引用该列),然后才在数据库中删除该列。

  • 修改列类型: 通常可以在线执行(如 ALTER TABLE ... MODIFY COLUMN),但 MySQL 8.0+ 使用 ALGORITHM=INPLACEINSTANT 可以减少锁表时间,在低峰期执行。

  • 使用迁移工具的“检查点”机制: Laravel、Symfony 等框架的迁移工具通常支持回滚,在执行迁移前,务必确保你的部署管道可以在失败时自动回滚迁移。


最佳实践清单

  1. 首选策略: 负载均衡 + 蓝绿部署(或容器滚动更新)。
  2. 数据库为先: 确保所有 SQL 变更向前兼容(只增不改,先加后删)。
  3. 自动化: 使用 CI/CD 工具(如 Jenkins, GitLab CI, GitHub Actions)自动构建、测试、部署,减少人工操作失误。
  4. 监控: 部署后立即监控应用日志、错误率和服务器负载。
  5. 快速回滚: 蓝绿部署秒级回滚;滚动更新 kubectl rollout undo;软链接切回旧目录。
  6. Opcache: 无论哪种非容器化方案,部署后务必清除 Opcache。
  7. 缓存预热: 如果有应用层缓存(如 Redis),新版本部署后可以先填充热点数据,避免首次请求穿透缓存导致数据库压力。

一句话建议: 如果你有运维资源,蓝绿部署 + 向前兼容的数据库迁移是最稳妥、最无感的方案。

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