两回合制 PHP 项目首回合部署方案
在两回合制部署(如灰度发布、蓝绿部署、A/B 双版本并存)中,首回合部署的核心目标是:让新版本能在不影响现有服务的前提下上线,并具备随时回滚的能力。

下面给出一个完整的 PHP 项目首回合部署方案。
部署模型选择
常见两回合制部署有两种形态:
| 模型 | 首回合含义 | 适用场景 |
|---|---|---|
| 蓝绿部署 | 首回合把新版本部署到"绿"环境,流量仍走"蓝" | 要求零停机、可秒级回滚 |
| 灰度/金丝雀 | 首回合只放小比例流量到新版本 | 大流量、需真实流量验证 |
首回合的共同点:
- 新版本部署到独立环境/路径(不覆盖线上)
- 数据层兼容(新旧版本能同时读写)
- 流量切换开关就绪但默认不切或小比例切
- 回滚路径已准备好
首回合部署流程(以蓝绿为例)
环境隔离
/var/www/
├── blue/ # 当前线上版本 (v1)
├── green/ # 首回合要部署的新版本 (v2)
└── shared/ # 共享资源
├── .env
├── storage/ # 日志、缓存、上传
└── sessions/
Nginx 通过软链切换:
root /var/www/current/public;
代码交付(不覆盖线上)
# 拉取新代码到 green cd /var/www/green git fetch origin git checkout v2.0.0 # 复用共享资源(软链) ln -sfn /var/www/shared/.env .env ln -sfn /var/www/shared/storage storage ln -sfn /var/www/shared/sessions ../../shared/sessions
依赖安装(隔离于 blue)
composer install --no-dev --optimize-autoloader --no-interaction
⚠️ 不要执行
composer update,使用composer.lock保证可复现。
数据层兼容处理(关键)
首回合最大风险是数据库结构变更,原则:
- 只加不改:新增字段、新增表;不删字段、不改字段类型
- 可空默认值:新字段必须有默认值或允许 NULL
- 双写/双读兼容:旧代码遇到新字段无感知
// 迁移脚本示例:只做加法
Schema::table('orders', function (Blueprint $table) {
$table->string('channel')->nullable()->after('status');
});
执行迁移时:
php artisan migrate --force # Laravel # 或 php bin/console doctrine:migrations:migrate --no-interaction # Symfony
缓存/配置预热(针对 green)
php artisan config:cache php artisan route:cache php artisan view:cache php artisan opcache:compile # 若使用
注意:Laravel 的 config:cache 会写进 green 自己的 bootstrap/cache,不影响 blue。
健康检查(切换前必做)
通过内网直接访问 green,不经过线上入口:
curl -H "Host: www.example.com" http://127.0.0.1:8081/health # 或指向 green 的本地 Nginx curl http://green.internal/health
健康检查应包含:
- HTTP 200
- 关键依赖(DB/Redis/MQ)可连
- 版本号正确
// /health 路由
Route::get('/health', function () {
DB::connection()->getPdo();
Redis::ping();
return response()->json([
'status' => 'ok',
'version' => config('app.version'),
]);
});
首回合收尾:不切或小比例切
蓝绿:current 软链保持不变,流量仍走 blue;等待第二回合正式切换。
# 此时保持: # current -> /var/www/blue # 不做任何切换
灰度:在 Nginx/SLB 层放小比例流量:
upstream backend {
server 127.0.0.1:8080 weight=95; # blue
server 127.0.0.1:8081 weight=5; # green
}
首回合部署脚本示例(可落地)
deploy.sh:
#!/usr/bin/env bash
set -euo pipefail
NEW_DIR="/var/www/green"
SHARED="/var/www/shared"
REPO="git@git.example.com:app.git"
TAG="$1"
echo "==> 1. 拉取代码"
cd "$NEW_DIR"
git fetch --tags
git checkout "$TAG"
echo "==> 2. 挂载共享资源"
ln -sfn "$SHARED/.env" .env
ln -sfn "$SHARED/storage" storage
echo "==> 3. 安装依赖"
composer install --no-dev --optimize-autoloader --no-interaction
echo "==> 4. 数据库迁移(向后兼容)"
php artisan migrate --force
echo "==> 5. 预热缓存"
php artisan config:cache
php artisan route:cache
echo "==> 6. 健康检查"
for i in {1..5}; do
if curl -sf http://green.internal/health >/dev/null; then
echo "健康检查通过"
break
fi
sleep 2
[ "$i" = "5" ] && { echo "健康检查失败"; exit 1; }
done
echo "==> 首回合完成:green 就绪,等待第二回合切换"
首回合必须注意的坑
| 项 | 说明 |
|---|---|
| Session 共享 | 若用文件 Session,务必走 shared 目录或改 Redis,否则切换时用户掉线 |
| 上传目录 | 必须共享,不能跟着代码版本走 |
| 日志 | 新旧版本日志分离,便于排查,但要有统一聚合 |
| Cron / Queue | 首回合只跑旧版本的 worker,避免新任务对旧代码不可读 |
| 迁移不可逆 | 首回合迁移只能"加",为了第二回合能回滚,禁止 dropColumn |
| OPcache | 若开启,务必检查 green 独立 OPcache,防止缓存串版本 |
| .env 差异 | 新版本新配置项要有默认值或提前写入 shared/.env |
| CDN / 静态资源 | 带 hash 文件名,避免新旧资源冲突 |
首回合结束的"验收清单"
- [ ] green 代码部署完成,软链共享资源正常
- [ ] composer 依赖安装完成,无版本漂移
- [ ] 数据库迁移已执行,旧版本仍能正常运行
- [ ] 缓存已预热
- [ ] 内网健康检查通过
- [ ] 日志可正常写入
- [ ] Cron/Queue 仍指向 blue
- [ ] 流量仍集中在 blue(或仅 5% 在 green)
- [ ] 回滚脚本已准备(只需切软链或权重)
- [ ] 监控面板已加入 green 维度
总结一句话
首回合 = "部署但不上量":把新版本完整地放到旁路环境,验证通过、数据兼容,流量不动;第二回合才做原子切换或逐步放量,回滚能力在首回合就必须具备,否则两回合制就失去了意义。
如果你能说明具体场景(Laravel/Symfony/自研?K8s 还是裸机?灰度还是蓝绿?),我可以把方案细化到可直接执行的脚本。