本文目录导读:

- 网关层基于 Cookie/Header 的蓝绿分组(最常用,零侵入)
- 基于服务注册中心的 API 网关动态路由(适合微服务架构)
- PHP 应用层自实现流量分组(适合无网关场景)
- 基于 Docker/K8s 的 Service Mesh 方案(生产级)
- 最佳实践建议
- 关键注意事项
针对 PHP 项目的蓝绿部署,要实现微服务架构下的流量分组(即灰度发布/金丝雀发布),关键在于利用网关层(如 Nginx/OpenResty、Kong、APISIX)或服务网格(如 Istio)的流量控制能力,根据请求特征(如 Header、Cookie、IP、用户ID)将流量动态路由到不同版本的服务组。
以下是具体的技术实现方案,按从简单到复杂的顺序排列:
网关层基于 Cookie/Header 的蓝绿分组(最常用,零侵入)
核心原理: 在 Nginx 层根据请求携带的特定 Header(如 X-Canary: true)或 Cookie(如 version=green)将请求路由到对应的 PHP-FPM/应用容器组。
适用场景: 内部测试、给特定员工/用户开启新功能。
配置示例(基于 Nginx + OpenResty Lua):
upstream blue_group {
server 10.0.0.1:9000; # 旧版 PHP-FPM
server 10.0.0.2:9000;
}
upstream green_group {
server 10.0.0.3:9000; # 新版 PHP-FPM
server 10.0.0.4:9000;
}
# 默认路由到蓝组
upstream default_group {
server 10.0.0.1:9000;
server 10.0.0.2:9000;
}
server {
listen 80;
server_name api.example.com;
set $group "default_group";
# 根据 Header 决定分组
if ($http_x_canary = "true") {
set $group "green_group";
}
# 根据 Cookie 决定分组(用户ID 0-1000 走新版)
# 这里需要 Lua 脚本逻辑来解析 Cookie 中的 user_id 并取模
# 使用 Lua 代码示例(需安装 lua-resty-cookie):
# access_by_lua_block {
# local cookie = require "resty.cookie"
# local ck = cookie:new()
# local uid = ck:get("user_id")
# if uid and tonumber(uid) % 100 < 10 then -- 10% 流量
# ngx.var.group = "green_group"
# end
# }
location / {
proxy_pass http://$group;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
优点: 无需修改 PHP 代码,仅在网关层配置。
基于服务注册中心的 API 网关动态路由(适合微服务架构)
核心原理: 当 PHP 服务注册到 Consul/Etcd/Nacos 时,携带 Metadata(如 version: v2, stage: canary),API 网关(Kong/APISIX)根据路由规则中的 Weight 或 Header 匹配到特定元数据的实例。
配置示例(以 APISIX + Consul 为例):
- 服务注册:PHP 服务启动时,向 Consul 注册两个服务名(如
user-service),但标签不同:- 实例 A:
version=1.0,weight=90 - 实例 B:
version=2.0,weight=10
- 实例 A:
- APISIX 路由配置:
# 路由1:默认路由到所有实例 - uri: /api/user/* upstream: service_name: user-service discovery_type: consul # 默认按 Consul 的权重负载均衡- 或者更精细的控制(需要 APISIX 的
traffic-split插件):plugins: traffic-split: rules: - weight: 90 upstream_id: <blue-upstream-id> - weight: 10 upstream_id: <green-upstream-id> # 适用于跨版本完全不同的代码
- 或者更精细的控制(需要 APISIX 的
优点: 支持动态权重(蓝绿切换无需重启 Nginx),适合大规模集群。
PHP 应用层自实现流量分组(适合无网关场景)
如果无法控制网关,可以在 PHP 应用入口(如 index.php)读取公共配置(Redis/Redis/DB),根据用户特征决定使用哪个版本的业务逻辑。
代码示例(入口层):
<?php
// config/feature.php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 从 Redis 获取灰度配置,{"blue":90, "green":10}
$trafficConfig = json_decode($redis->get('traffic_split'), true);
// 根据用户ID 或 随机数 决定分组
$userID = $_COOKIE['user_id'] ?? $_SERVER['REMOTE_ADDR'];
$hash = crc32($userID) % 100;
if ($hash < ($trafficConfig['green'] ?? 0)) {
// 走新版本逻辑
require_once 'app_v2.php';
} else {
require_once 'app_v1.php';
}
优点: 完全自主可控;缺点: 每次请求增加一次 Redis 查询,且需要维护两套代码入口。
基于 Docker/K8s 的 Service Mesh 方案(生产级)
在 Kubernetes 集群中部署 PHP 服务时(PHP-FPM 作为 Pod),利用 Istio 的 VirtualService 和 DestinationRule 实现流量灰度。
配置示例:
# DestinationRule:定义两个子集
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: php-service
spec:
host: php-service
subsets:
- name: blue
labels:
version: v1
- name: green
labels:
version: v2
---
# VirtualService:根据 Header 路由 20% 流量到 green
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: php-service
spec:
hosts:
- php-service
http:
- match:
- headers:
x-canary:
exact: "true"
route:
- destination:
host: php-service
subset: green
weight: 100
- route:
- destination:
host: php-service
subset: blue
weight: 80
- destination:
host: php-service
subset: green
weight: 20
优点: 无需修改网关配置,完全解耦;缺点: 需要 K8s 环境。
最佳实践建议
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 小项目、单机部署 | Nginx Header/Cookie 判断 | 简单、零侵入 |
| 中等规模、多服务器 | Nginx + Consul 动态路由 | 维护方便,支持动态权重 |
| 微服务、K8s 环境 | Istio VirtualService + K8s 部署 | 原生支持流量分组、自动恢复 |
| 老项目改造、无法动网关 | PHP 应用层 Redis 配置文件 | 快速实现,风险较低 |
关键注意事项
- 粘性会话(Session): PHP 使用文件 Session,需确保同一用户的请求始终路由到同一分组(否则 Session 会丢失),建议使用 Gateway 的
sticky模式或 Redis Session。 - 数据库/缓存隔离: 蓝绿版本的 PHP 代码应连接不同的数据库实例或不同的表前缀,避免数据污染,通常做法:
- 蓝版:
db_blue - 绿版:
db_green(测试数据)→ 验证无误后迁移到生产。
- 蓝版:
- 回滚设计: 流量分组应能快速切回 100% 到蓝组,在网关层配置中,优先使用 权重切换 而非直接修改
upstream地址。
PHP 的蓝绿部署 + 微服务流量分组,核心是在入口层(网关或应用入口)按业务特征分配流量,推荐从最简单的 Nginx Header 方案开始,随着架构演进逐步升级到 K8s + Istio 方案。