灰度切流:按用户 / 按租户 / 按百分比
一句话速记
灰度发布(Canary Release)= 把新版本先暴露给少量用户,观察效果,逐步扩大,有问题立即回滚,确保大规模故障不上线。核心设计要素:路由规则(谁能看到)+ 流量比例控制(多少人能看到)+ 监控告警(怎么知道出问题了)+ 快速回滚(出问题后怎么恢复)。
关键细节
1)灰度策略类型
按用户灰度:
适用:ToC 产品(用户量大,需要平滑验证)
方法 1:Hash 分桶
user_id % 100 = bucket_id
bucket_id < 5 → 新版本(5% 流量)
bucket_id >= 5 → 旧版本
优点:确定性(同一用户每次进同一桶),无需存储
缺点:bucket 分配不完全均匀(如果 user_id 不均匀)
方法 2:白名单(内测用户)
List<Long> betaUsers = [1001, 1002, 1003, ...];
if (betaUsers.contains(userId)) → 新版本
适用:先给内部员工/KOL/种子用户测试
方法 3:用户属性(精准圈选)
城市 = '北京' AND 注册天数 > 30 → 新版本
适用:特定用户群体(如新功能对某城市先开放)
按租户灰度(ToB 产品):
多租户 SaaS 产品(如企业微信功能灰度)
tenant_id 维度:
租户 A(小公司,愿意体验新功能)→ 新版本
租户 B(大企业,保守)→ 旧版本
配置中心管理:
grayscale_tenants: ["tenant-001", "tenant-002"] # YAML 配置
启用逻辑:
if tenant_id in grayscale_tenants → 新功能
优点:ToB 场景下影响面可控(一个租户出问题,不影响其他租户)
按百分比灰度(流量分割):
不区分用户,随机按比例分流
方法:
随机数分流:random() < 0.1 → 新版本(10% 随机流量)
问题:同一用户可能一次新版、一次旧版(体验不一致)
解决:结合 user_id Hash(保证用户粘性)
流量比例调整计划:
Day 1:1%(内部验证)
Day 2:5%(小规模用户验证)
Week 1:10%
Week 2:30%
Week 3:100%(全量放开)
遇到问题 → 立即回退到上一步百分比(快速回滚)
2)工程实现
网关层路由(推荐,统一控制):
// Spring Cloud Gateway 自定义过滤器
@Component
public class GrayRouteFilter implements GlobalFilter, Ordered {
@Autowired
private GrayRouteConfig grayConfig;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String userId = request.getHeaders().getFirst("X-User-Id");
// 灰度判断
if (isGrayUser(userId)) {
// 路由到灰度服务实例
URI newUri = URI.create("http://service-v2:" + request.getURI().getPath());
ServerHttpRequest newRequest = request.mutate().uri(newUri).build();
return chain.filter(exchange.mutate().request(newRequest).build());
}
return chain.filter(exchange); // 路由到正式服务
}
private boolean isGrayUser(String userId) {
if (userId == null) return false;
// 白名单优先
if (grayConfig.getWhitelist().contains(userId)) return true;
// Hash 分桶
int bucket = Math.abs(userId.hashCode()) % 100;
return bucket < grayConfig.getPercent(); // 如:10 = 10%
}
}配置中心动态调整(无需发布):
# Nacos / Apollo 配置(可动态推送)
grayscale:
enabled: true
percent: 10 # 流量百分比(随时调整)
whitelist: # 白名单(优先级高于 percent)
- "user-1001"
- "user-2002"
tenant_list: # 租户白名单
- "tenant-abc"@RefreshScope // Apollo/Nacos 支持动态刷新
@ConfigurationProperties(prefix = "grayscale")
public class GrayConfig {
private boolean enabled;
private int percent;
private List<String> whitelist;
private List<String> tenantList;
}服务端灰度(功能级开关):
// Feature Flag 模式(比网关路由粒度更细)
public class FeatureFlags {
@Autowired
private FlagConfig flagConfig;
public boolean isNewAlgorithmEnabled(Long userId) {
// 先查用户白名单
if (flagConfig.getWhitelistUsers().contains(userId)) return true;
// 按 userId Hash 分桶
int bucket = (int)(userId % 100);
return bucket < flagConfig.getNewAlgorithmPercent();
}
}
// 使用:
@Service
public class RecommendService {
public List<Item> recommend(Long userId) {
if (featureFlags.isNewAlgorithmEnabled(userId)) {
return newAlgorithm.recommend(userId); // 新算法
}
return oldAlgorithm.recommend(userId); // 旧算法
}
}3)监控与自动回滚
灰度监控指标:
对比新旧版本:
错误率(5xx):灰度 vs 非灰度
响应时间 p99:灰度 vs 非灧度
核心业务指标:下单转化率、支付成功率(不能因灰度下降)
告警阈值(自动触发):
灰度错误率 > 旧版本 2 倍 → 立即回滚(自动)
灰度 p99 延迟 > 旧版本 1.5 倍 → 告警(人工决策)
快速回滚:
网关层回滚(秒级):
修改配置:grayscale.percent = 0(立即停止灰度)
配置中心动态推送(无需发布代码)
K8s 层回滚:
kubectl rollout undo deployment/service-v2
或:把 canary 部署的实例数调为 0
功能开关回滚:
featureFlag.setEnabled(false)(关闭功能,旧版代码自动生效)
4)蓝绿发布 vs 灰度发布
蓝绿发布(Blue-Green):
同时运行两个完整环境(蓝=旧,绿=新)
切换:DNS/负载均衡一次性把流量切到绿
优点:切换和回滚都是秒级(DNS 切换)
缺点:需要双倍资源
适用:数据库无 schema 变更,或 schema 向前兼容的场景
灰度发布(Canary):
新旧版本同时运行,逐步增加新版本流量
优点:风险可控,逐步验证
缺点:两个版本同时维护的复杂性(DB 要兼容两个版本的操作)
适用:大多数互联网发布场景
金丝雀部署(Canary Deploy):
K8s 中:主 Deployment + Canary Deployment
通过 Service 的权重(Nginx Ingress / Istio)控制流量比
延伸追问
- Q:灰度期间,新旧版本共用同一个数据库,如何避免数据兼容性问题? → 数据库变更必须向前兼容(Expand-Contract 模式):先 ADD COLUMN(不 NOT NULL)→ 新代码写新列、旧代码不感知旧列 → 验证完成后 DROP 旧列。避免 RENAME COLUMN、改数据类型等破坏性变更在灰度期间上线。
- Q:灰度比例 10% 时,出了问题,10% 用户的数据怎么处理? → 这是灰度最大的风险:数据类问题(写入了脏数据)很难回滚。因此:1) 灰度前做好数据兼容性设计;2) 优先选”读”功能灰度(不写数据);3) 写数据灰度时,保证旧代码能兼容新格式数据(向后兼容);4) 出问题后需要手动数据修复(DBA 支持)。
- Q:如何实现”同一用户每次都路由到同一版本”(会话黏性)?
→ 用 userId Hash 分桶(确定性),相同 userId 每次哈希值相同,桶号不变,版本不变。避免用 sessionId(会话到期后会变)。也可以在第一次访问时,在 Cookie 中写入灰度标记(
gray=1),后续根据 Cookie 路由。
我的记法
- 灰度三要素:路由规则(谁)+ 比例控制(多少)+ 监控回滚(兜底)
- 按用户:userId Hash % 100 < percent → 新版本(确定性+无状态)
- 按租户:tenantId 白名单(ToB 常用)
- 回滚:配置中心动态推送,percent=0,秒级生效
- 蓝绿 = 双环境一次切换;灰度 = 单环境逐步放量
- 一句话:「灰度 = Hash 分桶路由 + 动态配置控比例 + 监控自动回滚」
状态
- 已背速记
- 能写 userId Hash 分桶代码
- 能解释灰度期间数据库兼容性处理