灰度发布深度解析
面试官:你了解灰度发布吗?
你:灰度发布是一种渐进式的发布策略,将新版本逐步推送给少量用户,验证功能和稳定性后再全量发布,降低发布风险。
面试官:蓝绿部署和金丝雀发布有什么区别?
链式追问一:发布策略
Section titled “链式追问一:发布策略”Q1:蓝绿部署(Blue-Green Deployment)是什么?高频
Section titled “Q1:蓝绿部署(Blue-Green Deployment)是什么?”原理:同时维护两套完全相同的生产环境(蓝色 = 当前版本,绿色 = 新版本)。
当前状态: 用户 → 负载均衡 → [蓝色环境 v1.0(全量流量)] [绿色环境 v2.0(空跑,无流量)]
发布步骤: 1. 在绿色环境部署并测试 v2.0 2. 切换负载均衡:将流量全量切到绿色环境 3. 蓝色环境保留(回滚备用)
回滚: 发现问题 → 一键切回蓝色环境(秒级回滚)优点:切换瞬间完成,回滚简单 缺点:需要双倍资源;新版本问题会影响全量用户
Q2:金丝雀发布(Canary Release)是什么?和蓝绿部署的区别?必考
Section titled “Q2:金丝雀发布(Canary Release)是什么?和蓝绿部署的区别?”名字由来:矿工用金丝雀探测矿井中的毒气,金丝雀先进入,有问题先撤。
原理:将少量流量(如 5%)引导到新版本,逐渐增加比例,直到全量切换。
阶段一(5% 流量到新版本): 用户 → 网关 → [v1.0 实例 × 19](95% 流量) [v2.0 实例 × 1] (5% 流量)
阶段二(20%)→ 阶段三(50%)→ 阶段四(100%)
监控每个阶段的错误率、响应时间、业务指标发现问题 → 立即将金丝雀流量缩减为 0(回滚)蓝绿 vs 金丝雀:
| 维度 | 蓝绿部署 | 金丝雀发布 |
|---|---|---|
| 资源需求 | 双倍 | 少量即可 |
| 风险范围 | 全量(切换瞬间) | 逐步扩大 |
| 回滚速度 | 瞬间(切回) | 缩减流量比例 |
| 验证方式 | 切前测试 | 真实用户小流量验证 |
| 适用场景 | 快速全量发布 | 重要功能谨慎发布 |
Q3:A/B 测试和金丝雀发布的区别?
Section titled “Q3:A/B 测试和金丝雀发布的区别?”| 维度 | A/B 测试 | 金丝雀发布 |
|---|---|---|
| 目的 | 验证哪个方案效果更好(业务决策) | 验证新版本是否稳定(技术发布) |
| 用户分流 | 按用户特征(地区/年龄/行为) | 按比例随机 |
| 持续时间 | 长(收集统计数据) | 短(确认稳定后快速全量) |
| 关注指标 | 业务指标(转化率/留存率) | 技术指标(错误率/延迟) |
链式追问二:网关层灰度实现
Section titled “链式追问二:网关层灰度实现”Q4:如何在 Spring Cloud Gateway 中实现灰度发布?高频
Section titled “Q4:如何在 Spring Cloud Gateway 中实现灰度发布?”方案一:基于 Header/Cookie 的灰度(定向灰度)
// 自定义灰度路由过滤器@Componentpublic class GrayFilter implements GlobalFilter, Ordered {
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest();
// 根据 Header 判断是否走灰度 String version = request.getHeaders().getFirst("X-Version"); if ("gray".equals(version)) { // 将请求转发到灰度实例(通过修改服务名或直接指定 IP) URI grayUri = URI.create("lb://user-service-gray"); ServerHttpRequest newRequest = request.mutate().uri(grayUri).build(); return chain.filter(exchange.mutate().request(newRequest).build()); }
return chain.filter(exchange); }}方案二:基于权重的灰度(随机比例)
spring: cloud: gateway: routes: - id: user-service-stable uri: lb://user-service predicates: - Path=/api/users/** - Weight=group1, 90 # 90% 流量 - id: user-service-canary uri: lb://user-service-canary predicates: - Path=/api/users/** - Weight=group1, 10 # 10% 流量方案三:结合注册中心的灰度(基于元数据)
# Nacos 实例元数据nacos: discovery: metadata: version: gray # 或 stable
# 灰度路由规则:将带有 X-Version: gray Header 的请求路由到 version=gray 的实例Q5:灰度发布中如何保证「粘性会话」(同一用户始终路由到同一版本)?高频
Section titled “Q5:灰度发布中如何保证「粘性会话」(同一用户始终路由到同一版本)?”问题:用户第一次请求落到新版本,第二次请求落到旧版本,可能出现状态不一致。
解决方案:
@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String userId = getUserId(exchange);
// 方案一:基于用户 ID 哈希(同一用户永远路由到同一比例) int hash = Math.abs(userId.hashCode() % 100); String targetService = hash < grayPercent ? "user-service-gray" : "user-service";
// 方案二:基于 Cookie(第一次分配后写入 Cookie) HttpCookie versionCookie = exchange.getRequest().getCookies() .getFirst("X-Service-Version"); String version = versionCookie != null ? versionCookie.getValue() : assignVersion(userId);
// 方案三:基于 Redis 记录用户版本分配 String assignedVersion = redis.get("user:version:" + userId); if (assignedVersion == null) { assignedVersion = random.nextInt(100) < grayPercent ? "gray" : "stable"; redis.set("user:version:" + userId, assignedVersion, 7, TimeUnit.DAYS); }}