本文目录导读:

- 核心思路
- 方案一:基于Nginx + 加权轮询 + 会话保持(最常用)
- 方案二:基于Kubernetes + Service Mesh(Istio/Linkerd)
- 方案三:基于网关层(Kong/Apisix/Traefik)
- 方案四:基于云原生负载均衡(阿里云SLB/腾讯云CLB + 后端服务器组)
- 注意事项(避坑指南)
- 总结建议
在PHP项目中进行灰度发布的流量负载分配,在负载均衡层面(如Nginx、HAProxy、云服务商的SLB)通常有以下几种主流方案和实现方式:
核心思路
灰度本质上是一种流量路由策略,在负载层面,核心是让负载均衡器根据某种规则(如Header、Cookie、IP、URL参数)将一部分请求路由到“灰度版本”的服务实例池,另一部分路由到“稳定版本”的服务实例池。
基于Nginx + 加权轮询 + 会话保持(最常用)
这是自建服务器或非容器化PHP项目最常见的方法,适用于通过upstream模块控制。
场景: 灰度版本和稳定版本部署在同一台或不同服务器上,监听不同端口。
实现步骤:
-
部署两个upstream池:
stable_pool:运行旧版本PHP代码的服务器组(权重100)。canary_pool:运行新版本PHP代码的服务器组(权重5)。
-
配置加权轮询:
upstream stable_pool { server 10.0.0.1:80 weight=100; server 10.0.0.2:80 weight=100; } upstream canary_pool { server 10.0.0.3:80 weight=5; # 灰度服务器 server 10.0.0.4:80 weight=5; # 灰度服务器 } server { listen 80; server_name example.com; # 方案A:通过Cookie/Header做灰度分流(精确控制) # 如果用户有 "canary=1" 的Cookie,则走灰度池 set $upstream_group "stable_pool"; if ($http_cookie ~* "canary=1") { set $upstream_group "canary_pool"; } # 或者根据IP段(如公司内网IP走灰度) # if ($remote_addr ~ "192.168.1.") { set $upstream_group "canary_pool"; } location / { proxy_pass http://$upstream_group; proxy_set_header Host $host; # 重要:关闭Nginx的proxy_pass头部重写,保持后端PHP拿到真实信息 } }如何按比例自动分流? 如果不想手动打标记,希望随机按比例(如5%流量走灰度),可以使用split_clients模块。
# 根据用户IP的Hash值,将5%的流量路由到灰度 split_clients "${remote_addr}${http_user_agent}" $canary_upstream { 5% canary_pool; * stable_pool; } server { location / { proxy_pass http://$canary_upstream; } }
优点: 配置灵活,基于任何请求属性(IP、UA、Cookie)分流。 缺点: 需要维护多组upstream,灰度版本和稳定版本需要共存在负载均衡配置中。
基于Kubernetes + Service Mesh(Istio/Linkerd)
对于容器化、微服务化的PHP项目(如Hyperf、Laravel配合Swoole或FPM部署在K8s),这是最先进的方案。
核心原则: 流量路由在Service Mesh的数据平面(Envoy代理)完成,而非应用层面。
实现步骤(以Istio为例):
-
部署两个Deployment标签不同版本:
app: my-php, version: v1(稳定版)app: my-php, version: v2(灰度版)
-
配置Istio VirtualService + DestinationRule:
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: my-php-vs spec: hosts: - my-php-service http: - match: - headers: x-canary: exact: "true" # 通过Header精确控制 route: - destination: host: my-php-service subset: v2 - route: - destination: host: my-php-service subset: v1 weight: 95 # 95%流量到v1 - destination: host: my-php-service subset: v2 weight: 5 # 5%流量到v2 --- apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: my-php-dr spec: host: my-php-service subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2
优点: 完全解耦应用,支持流量百分比、Header、Cookie、URI等多种策略,支持故障回滚(自动剔除不健康的灰度实例)。 缺点: 架构复杂,需要K8s和Istio成本。
基于网关层(Kong/Apisix/Traefik)
如果项目使用了API网关(如Kong、Apache APISIX、Traefik),可以在网关层实现灰度。
示例(APISIX):
- 在上游(Upstream)中定义两个节点(stable和canary)。
- 通过traffic-split插件:
{ "plugins": { "traffic-split": { "rules": [ { "weighted_upstreams": [ { "upstream": { "nodes": { "10.0.0.3:80": 5 // 灰度节点权重5 } }, "weight": 5 } ] } ] } } }或者只针对特定Header的请求分流:
"match": [{"vars": [["http_x_canary", "==", "true"]]}]
基于云原生负载均衡(阿里云SLB/腾讯云CLB + 后端服务器组)
在云服务器上,通常使用云厂商的负载均衡器。
- 步骤:
- 创建两个后端服务器组(RS Group):
- RS Group1(稳定版):绑定10台旧应用服务器。
- RS Group2(灰度版):绑定2台新应用服务器。
- 在负载均衡监听器中,默认转发到RS Group1。
- 灰度策略实现:
- 方式A(流量分配): 大部分云LB支持基于权重的后端服务器组转发(需要LB支持多目标组),老组权重80%,新组权重20%。
- 方式B(基于域名/URL): 创建两个监听器(如
test.example.com-> 灰度组,www.example.com-> 稳定组),或者创建路径转发规则(/new-feature/*-> 灰度组)。 - 方式C(基于Cookie/Header): 使用云LB的自定义转发策略(如阿里云ALB的转发规则),允许根据Header进行分流。
- 创建两个后端服务器组(RS Group):
注意事项(避坑指南)
- PHP Session一致性:灰度期间,同一用户的请求要尽量落在同一版本应用上,否则Session数据可能丢失,解决方案:
- 基于
Cookie(如canary_id)做一致性Hash。 - 使用Redis/Memcached共享Session(但需要注意新老版本的Session数据兼容性)。
- 基于
- 数据库/缓存兼容:灰度版本尽量不要修改数据库表结构或Redis key序列化方式,如果必须改,需要做双写或向后兼容。
- 日志/监控:灰度版本和稳定版本的日志要分开收集(不同Logstash/tag),监控指标(错误率、延迟)要分开告警。
- 自动回滚:在灰度比例从5%提升到50%前,设立自动检测机制,如果灰度版本错误率 > 1%,自动将权重归零,流量回到稳定版。
总结建议
| 项目类型 | 推荐方案 | 理由 |
|---|---|---|
| 传统PHP项目(LNMP,非容器化) | Nginx split_clients + upstream | 简单、零成本、基于IP/Header随机分配5%流量。 |
| 容器化PHP项目(K8s + Docker) | Istio/Knative | 与业务代码解耦,支持零停机发布、自动回滚。 |
| API网关型项目 | Kong/Apisix的traffic-split插件 | 强大的流量管理能力,支持权重/Header/Cookie组合条件。 |
| 云服务器(无K8s) | 云LB的多目标组 + 按权重转发 | 原生支持,免运维,支持灰度步进提升。 |
最终建议: 对于大多数PHP团队,Nginx + split_clients + 灰度版本部署在不同端口 是性价比最高、最易上手的方法。