本文目录导读:

- 方案一:负载均衡 + 蓝绿部署(最推荐、最成熟)
- 方案二:滚动更新(基于容器化)
- 方案三:利用 PHP-FPM 的平滑重载(较简单,但有局限)
- 方案四:基于 Git 的分支部署 + 预发布 Hook(适合极简场景)
- 数据库迁移:零停机部署的最大挑战
- 最佳实践清单
在 PHP 项目发布期间,保证服务不中断(即实现零停机部署或平滑发布)的核心思路是:让旧版本继续服务,直到新版本完全就绪并能接管流量。
由于 PHP 通常是无状态、每次请求重新加载的语言(除非使用 Swoole/Workerman 等常驻内存模式),其部署策略与 Java/Node.js 有所不同,但同样可以实现零停机。
以下是几种常用的、行之有效的方案,按推荐程度和复杂度排序:
负载均衡 + 蓝绿部署(最推荐、最成熟)
这是最标准、最可靠的零停机方案,适用于大多数 Web 应用。
原理: 准备两组完全独立的应用服务器集群,一组是“蓝色”(当前生产环境),一组是“绿色”(新版本环境),通过负载均衡器(Nginx、HAProxy、云服务商 SLB)瞬间切换流量。
实现步骤:
-
环境准备:
- 蓝组: 当前正在运行的生产环境,对外提供服务。
- 绿组: 与蓝组完全相同的配置(操作系统、Web 服务器、PHP 版本、扩展、数据库连接等),但代码是新版本。绿组在部署期间不对外服务。
-
数据库兼容:
- 关键点: 新代码必须向下兼容旧代码,即:数据库的变更(增删字段、表)必须保证蓝组代码依然能正常运行。
- 实践: 采用“先加后删”策略,先给表添加一个新字段(新字段允许为空或默认值),部署绿组代码使用该字段,待蓝绿切换完成,旧代码下线后,再移除不需要的旧字段。
-
部署流程:
- 在绿组上部署新版本 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_green,nginx -s reload。
滚动更新(基于容器化)
适用于使用 Docker/Kubernetes 的场景,这是容器编排平台(K8s、Docker Swarm)的原生能力。
原理: 逐步用新版本 Pod 替换旧版本 Pod,每次替换一个或一批,确保始终保持一定数量的 Pod 在服务中。
实现步骤(以 K8s 为例):
- 构建新镜像: 将新代码打包成 Docker 镜像,打上版本标签(如
v2.0.1)。 - 更新 Deployment: 修改 Deployment 的镜像版本,K8s 会自动执行
RollingUpdate策略。 - 过程:
- K8s 先启动一个或多个新 Pod(包含新代码)。
- 等待新 Pod 通过
readinessProbe(就绪探针)检测,确认可以接受流量。 - Service 负载均衡器自动将流量分发到新 Pod。
- K8s 优雅地(发送
SIGTERM信号,让 PHP-FPM 处理完当前请求)停止一个或多个旧 Pod。 - 重复此过程,直到所有旧 Pod 被替换。
关键配置:
strategy.type: RollingUpdatemaxSurge:更新期间可以超过期望副本数的最大 Pod 数(通常设为 25%)。maxUnavailable:更新期间允许不可用的最大 Pod 数(通常设为 0 或 25%)。readinessProbe:必须仔细配置,用httpGet请求你的 PHP 应用的/health端点,只有当新版本代码健康时才加入流量。
优点:
- 资源利用率高,不需要额外服务集群。
- 自动化程度高,K8s 原生支持。
- 回滚方便(
kubectl rollout undo)。
缺点:
- 对 Docker/K8s 运维能力有要求。
- 更新过程中,新旧版本会短暂共存,需要代码兼容性。
- 数据库变更仍需谨慎处理。
利用 PHP-FPM 的平滑重载(较简单,但有局限)
适用于单服务器或小型应用。
原理: PHP-FPM 支持 reload 信号,它会:
- 读取新配置文件。
- 启动新的 Worker 进程组,使用新代码。
- 优雅地等待旧 Worker 进程处理完当前请求后,再将其终止。
实现方式:
-
代码部署: 先将新代码上传到服务器的临时目录(
/var/www/app_v2/)。 -
软链接切换: 修改一个软链接(
ln -sfn /var/www/app_v2 /var/www/current),让 Web 服务器(如 Nginx)的root或document_root指向新代码。这步本身是原子的,瞬间完成。
-
清除 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。
- 配置
-
优雅重载: 执行
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 ...,应该:- 新增一个
new_name列,类型与旧列相同,允许为空或有默认值。 - 部署新版本代码,写入
new_name和old_name,读取时优先或只读new_name。 - 运行一个修复脚本,将
old_name的数据同步到new_name。 - 删除
old_name列(在下次部署或独立维护窗口中进行)。
- 新增一个
-
删除列: 先部署新代码(不再引用该列),然后才在数据库中删除该列。
-
修改列类型: 通常可以在线执行(如
ALTER TABLE ... MODIFY COLUMN),但 MySQL 8.0+ 使用ALGORITHM=INPLACE或INSTANT可以减少锁表时间,在低峰期执行。 -
使用迁移工具的“检查点”机制: Laravel、Symfony 等框架的迁移工具通常支持回滚,在执行迁移前,务必确保你的部署管道可以在失败时自动回滚迁移。
最佳实践清单
- 首选策略: 负载均衡 + 蓝绿部署(或容器滚动更新)。
- 数据库为先: 确保所有 SQL 变更向前兼容(只增不改,先加后删)。
- 自动化: 使用 CI/CD 工具(如 Jenkins, GitLab CI, GitHub Actions)自动构建、测试、部署,减少人工操作失误。
- 监控: 部署后立即监控应用日志、错误率和服务器负载。
- 快速回滚: 蓝绿部署秒级回滚;滚动更新
kubectl rollout undo;软链接切回旧目录。 - Opcache: 无论哪种非容器化方案,部署后务必清除 Opcache。
- 缓存预热: 如果有应用层缓存(如 Redis),新版本部署后可以先填充热点数据,避免首次请求穿透缓存导致数据库压力。
一句话建议: 如果你有运维资源,蓝绿部署 + 向前兼容的数据库迁移是最稳妥、最无感的方案。