我前阵子接手了一个访问量不太稳定的老项目,上线新功能经常要提心吊胆地盯半天日志,生怕出点线上事故。后来我把服务迁到Swoole常驻内存之后,顺便把灰度发布和A/B测试的路由方案一起做了。这套方案跑下来,最大的感受就是:当你能在应用层直接决定"这个请求走哪套逻辑"的时候,很多运维层面的麻烦事都会被前置消化掉。这篇文章就把我完整的实现思路、代码结构和踩坑记录整理出来,给正在折腾Swoole路由分发的朋友一个参考。
1. 先搞清楚灰度发布与A/B测试到底在解决什么问题
1.1 灰度发布不是"小流量上线"这么简单
灰度发布最容易让人误解的地方在于,以为只要控制一台机器、放一点流量进去就算灰度了。实际上灰度发布的核心诉求是风险隔离与快速回退。传统的一台一台滚动发布,问题在于如果新版本有Bug,你只能在"发现异常→停止发布→回滚上一版"这条链路里被动响应,整个过程可能有几分钟甚至更长的空窗期,对线上用户的影响是不可控的。
而在应用层做灰度,意味着你可以把请求实时地分到新旧两套逻辑上。同一台机器上、同一个进程里,老代码和新代码可以共存,通过路由规则决定当前这个请求由谁来处理。这样即使新逻辑出了问题,你改一条规则、刷一下配置就能切回老逻辑,回退粒度是"单次请求"级别的,而不是"整台机器"级别的。
1.2 A/B测试的决策逻辑和灰度完全不同
灰度发布关注的是"稳",A/B测试关注的是"证"。灰度发布不需要纠结用户最终落在哪个版本上,只要保证流量逐步放大且系统稳定即可。A/B测试则需要严谨的分组逻辑:同一个人在不同时间进入系统,必须始终命中同一个实验组,否则试验数据就废了。
这一点直接决定了路由策略的选型。灰度发布可以用随机数、IP哈希、用户ID取模这种粗粒度策略,A/B测试则必须引入稳定的用户分桶机制。我在设计路由组件时,把这两类需求都抽象成了统一的规则引擎,只是底层决策函数不同,这个后面详细说。
1.3 为什么这个方案要放在Swoole这一层做
放在Nginx层做灰度也可以,通过Set-Cookie或者X-User-Id做条件转发,但问题是Nginx的规则写起来很受限,一旦牵扯到多级灰度条件(比如命中白名单、再按百分比、再按地域),配置项就会变得很臃肿且难维护。放在DNS层做灰度就更粗了,完全是机房级别的迁移,不适用于单功能级别的实验。
Swoole常驻内存的特点带来一个天然优势:路由决策可以发生在请求被处理之前,而且决策所需的数据可以全部缓存在进程内。不需要走一层外部网络请求来确定"这个用户该看哪个版本",决策延迟可以降到微秒级。此外Swoole支持自定义进程、Table、协程等特性,可以很方便地把规则存储和热更新能力做进一个内部组件里,这是传统PHP-FPM架构很难实现的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体架构设计:路由决策层应该长什么样
2.1 规则模型设计:灰度规则的数据结构
我一开始犯过的一个错误,是想把灰度规则做得特别复杂,支持任意维度的条件组合。后来发现生产环境根本用不到那么多花活,最终沉淀下来了一套比较收敛的规则模型:
json复制{
"feature": "new_checkout",
