PHP项目滚动发布如何分批更新集群节点

wen PHP项目 23

本文目录导读:

PHP项目滚动发布如何分批更新集群节点

  1. 核心策略:先摘流,后更新,再入流
  2. 必须解决的3个关键问题
  3. 生产脚本示例(简化版)
  4. 总结建议

对于PHP项目的滚动发布(Rolling Update),核心目标是在不中断服务的前提下,逐步替换集群中的节点,由于PHP本身是无状态的(不存储Session在本地),这比Java等有状态应用要简单得多。

下面是一个标准的、生产级的PHP项目分批更新集群节点的方案。

核心策略:先摘流,后更新,再入流

假设你有一个负载均衡器(如Nginx, HAProxy, AWS ALB)后面挂了N个PHP-FPM节点。

第一阶段:准备(代码与配置)

  1. 代码仓库:确保最新代码已合并到部署分支(如 releasemain)。
  2. 构建产物
    • 强烈建议进行本地构建(如Composer安装、前端打包、生成Symfony缓存预热等),然后只将构建后的dist/vendor/文件夹同步到服务器,不要在服务器上直接composer install,这能显著减少更新窗口。
  3. 版本标签:为这次发布打上Git Tag(如 v2.3.1)。

第二阶段:分批更新(核心步骤)

方案A:基于负载均衡器摘流(推荐,最通用)

这是最标准、对用户影响最小的方式,步骤如下(以Nginx+PHP-FPM,4个节点为例):

  1. 节点分组:将4个节点分为2批(根据你的容忍度,可以是1/41/21/3)。

    • Batch 1:web-node-01, web-node-02
    • Batch 2:web-node-03, web-node-04
  2. 摘除Batch 1(Drain)

    • 在负载均衡器(Nginx)或云服务控制台(如AWS ALB Target Group)中,将Batch 1的节点标记为“down”或从上游池移除
    • 关键操作:发送SIGUSR2信号给PHP-FPM的master进程,让FPM优雅退出,这会告诉FPM停止接受新请求,但等待当前正在处理的请求完成(由pm.max_requestsprocess_control_timeout控制)。
      # 命令示例(根据系统配置调整路径)
      sudo kill -USR2 $(cat /var/run/php-fpm.pid)
    • 等待:等待30-60秒(或监控FPM连接数降到0),确保该节点没有活跃请求。
  3. 更新Batch 1(Update)

    • 将新代码(构建后的产物)部署到Batch 1的服务器上。
    • 重新加载PHP-FPM(使用SIGHUPservice php8.1-fpm reload),这会启动新的worker进程,加载新代码。
    • 清除本地OPcache(opcache_reset())或重启PHP-FPM(如果代码改动涉及核心扩展)。
    • 快速验证:通过内网IP或修改本地Hosts文件,直接访问该节点的某个健康检查URL(如/health.php),确保接口返回200且版本号正确。
  4. 重新接入Batch 1(Join)

    • 在负载均衡器中,将Batch 1的节点重新标记为“up”,重新接受流量。
    • 监控:观察错误率和延迟1-2分钟。
  5. 处理Batch 2

    重复步骤2-4,更新Batch 2。

方案B:基于Kubernetes(自动化)

如果你的集群是K8s,过程完全自动化,你只需:

  1. 更新Deployment的镜像Tag(或ConfigMap)。
  2. K8s会创建新的Pod(新代码)。
  3. Service的readinessProbe会检查新Pod是否就绪。
  4. 默认策略是RollingUpdate,参数如:
    spec:
      replicas: 4
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxSurge: 1        # 最大可超过期望副本数1个
          maxUnavailable: 1  # 最大允许不可用副本数为1个
  5. 它会逐个(或按比例)替换Pod,直到所有都是新版本。

必须解决的3个关键问题

Session一致性问题

  • 问题:用户登录后,Session文件存在于旧节点A,滚动更新后,用户请求被路由到新节点B。
  • 解决方案(N选1)
    • Redis/Memcached:将Session存储到集中式缓存中(标准方案)。
    • Sticky Session:在负载均衡器开启Session亲和性(不推荐用于高可用)。
    • 无状态JWT:完全不用Session,用Token。

数据库兼容性(最难的点)

  • 问题:新代码需要数据库新增字段,而旧代码仍在运行。
  • 解决方案
    • 向前兼容:新代码必须兼容旧数据库结构,即数据库迁移必须分为两步:
      • Step 1:先执行ADD COLUMN name VARCHAR(255) NULL(可空),新代码读写新字段,旧代码忽略它(没问题)。
      • Step 2:等所有节点都更新为新版后,再执行ALTER TABLE ... SET NOT NULL或删除旧字段。
    • 金丝雀发布:只更新1个节点,让新代码运行一段时间,验证数据库没有死锁或慢查询。

OPcache 和 代码缓存

  • 问题:PHP代码被OPcache缓存,即使文件被替换,OPcache仍返回旧代码。
  • 解决方案
    • 方案A(推荐):部署脚本末尾执行 touch 命令修改opcache.revalidate_freq对应的文件时间戳,或直接重启PHP-FPM。service php8.1-fpm reload 会触发OPcache重置。
    • 方案B:使用opcache_reset()函数,放在健康检查URL中。
    • 方案C:在代码版本号中添加查询字符串,强制浏览器和OPcache不缓存?不行,OPcache基于文件路径缓存,不是URL。

生产脚本示例(简化版)

#!/bin/bash
# 假定有一个负载均衡器API,通过 curl 操作
set -e
CLUSTER=("192.168.1.10" "192.168.1.11" "192.168.1.12" "192.168.1.13")
BATCH_SIZE=2  # 每次更新2台
for (( i=0; i<${#CLUSTER[@]}; i+=BATCH_SIZE )); do
    BATCH=("${CLUSTER[@]:i:BATCH_SIZE}")
    echo "正在更新批次: ${BATCH[*]}"
    # 1. 摘流 (假设有一个API)
    for node in "${BATCH[@]}"; do
        curl -X POST "http://load-balancer-api:8080/drain?ip=$node"  # 移除节点
        # 2. 优雅关闭PHP-FPM
        ssh deployer@$node "sudo kill -USR2 \$(cat /var/run/php8.1-fpm.pid)"
        sleep 10  # 等待请求完成
    done
    # 3. 部署代码
    for node in "${BATCH[@]}"; do
        # rsync 新代码 (只同步变更文件,推荐)
        rsync -avz --delete /path/to/new/code/ deployer@$node:/var/www/html/
        # 或者解压tar包
        # ssh deployer@$node "tar xzf /tmp/release.tgz -C /var/www/html"
    done
    # 4. 重载PHP (新worker加载新代码)
    for node in "${BATCH[@]}"; do
        ssh deployer@$node "sudo service php8.1-fpm reload"
        # 验证健康
        curl -f "http://$node:80/health.php" || exit 1
    done
    # 5. 重新入流
    for node in "${BATCH[@]}"; do
        curl -X POST "http://load-balancer-api:8080/join?ip=$node"
    done
    echo "批次完成,等待观察..."
    sleep 30
done
echo "所有节点更新完成"

总结建议

场景 推荐策略 关键工具
物理机/VM + 手动 方案A:LB摘流 + 优雅退出 Nginx Upstream, kill -USR2, rsync
Kubernetes 使用K8s原生RollingUpdate Deployment, ReadinessProbe
云原生(AWS/GCP) 使用自动伸缩组 + 启动配置/模板 ASG Rolling Update, ALB Target Group

底线:无论哪种方式,务必先在预发环境演练,特别是要测试数据库迁移的回滚脚本。

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