第482集灰度/金丝雀发布怎么做?
|字数总计:5.3k|阅读时长:23分钟|阅读量:
灰度/金丝雀发布怎么做?
1. 概述
1.1 灰度/金丝雀发布的重要性
灰度发布(Gray Release)和金丝雀发布(Canary Release)是微服务架构中重要的发布策略,用于降低发布风险,提高系统稳定性。
发布策略的意义:
- 降低发布风险:逐步发布,及时发现和解决问题
- 提高系统稳定性:避免全量发布导致的系统故障
- 快速回滚:发现问题后快速回滚,减少影响范围
- 用户体验:保证大部分用户不受影响
1.2 发布策略分类
常见发布策略:
- 全量发布(Rolling Release):一次性发布所有实例
- 蓝绿部署(Blue-Green Deployment):同时运行两个版本,切换流量
- 灰度发布(Gray Release):按比例逐步发布新版本
- 金丝雀发布(Canary Release):先发布少量实例,验证后逐步扩大
1.3 本文内容结构
本文将从以下几个方面全面解析灰度/金丝雀发布:
- 灰度发布原理:什么是灰度发布、为什么需要灰度发布
- 金丝雀发布原理:什么是金丝雀发布、与灰度发布的区别
- 实现方式:基于网关、基于服务注册中心、基于负载均衡等
- 流量分配策略:按比例、按用户、按地域等分配策略
- 版本管理:版本标识、版本路由、版本回滚
- 监控告警:发布监控、指标收集、告警机制
- 实战案例:实际项目中的灰度/金丝雀发布实现
2. 灰度发布原理
2.1 什么是灰度发布
2.1.1 定义
灰度发布(Gray Release):将新版本逐步发布给部分用户,验证无问题后逐步扩大范围,最终全量发布。
特点:
- 逐步发布,降低风险
- 可以按比例控制流量
- 支持快速回滚
- 适合大规模系统
2.1.2 发布流程
灰度发布流程:
- 准备阶段:部署新版本到部分实例
- 灰度阶段:将部分流量切换到新版本
- 验证阶段:监控新版本运行情况
- 扩大阶段:逐步增加流量比例
- 全量阶段:所有流量切换到新版本
- 回滚阶段:发现问题后快速回滚
2.2 为什么需要灰度发布
2.2.1 降低发布风险
全量发布的风险:
- 新版本有Bug,影响所有用户
- 性能问题导致系统崩溃
- 数据兼容性问题
灰度发布的优势:
2.2.2 提高系统稳定性
灰度发布的优势:
- 逐步验证新版本
- 监控新版本指标
- 及时发现问题并处理
3. 金丝雀发布原理
3.1 什么是金丝雀发布
3.1.1 定义
金丝雀发布(Canary Release):先发布新版本到少量实例(金丝雀),验证无问题后逐步扩大范围。
名称来源:煤矿工人用金丝雀检测有毒气体,如果金丝雀死亡,说明有危险。
特点:
- 先发布少量实例
- 验证无问题后扩大
- 风险最小
- 适合关键系统
3.1.2 与灰度发布的区别
区别:
- 灰度发布:按比例逐步发布(如10% → 50% → 100%)
- 金丝雀发布:先发布少量实例,验证后全量发布
适用场景:
- 灰度发布:适合大规模系统,需要逐步验证
- 金丝雀发布:适合关键系统,需要最小风险
3.2 金丝雀发布流程
金丝雀发布流程:
- 准备阶段:部署新版本到1-2个实例
- 金丝雀阶段:将少量流量切换到新版本
- 验证阶段:监控新版本运行情况(错误率、响应时间等)
- 扩大阶段:验证通过后,逐步增加实例和流量
- 全量阶段:所有实例和流量切换到新版本
- 回滚阶段:发现问题后快速回滚
4. 实现方式
4.1 基于网关的灰度发布
4.1.1 实现原理
基于网关的灰度发布:在网关层根据规则将流量路由到不同版本的服务。
优势:
- 集中控制,易于管理
- 支持多种路由规则
- 不影响服务本身
4.1.2 Spring Cloud Gateway实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
| @Configuration public class GrayReleaseConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("order-service", r -> r .path("/api/order/**") .filters(f -> f .filter(new GrayReleaseGatewayFilter()) ) .uri("lb://order-service") ) .build(); } }
@Component public class GrayReleaseGatewayFilter implements GatewayFilter { @Autowired private GrayReleaseService grayReleaseService; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String userId = getUserId(request); String version = getVersion(request); String targetVersion = grayReleaseService.getTargetVersion("order-service", userId, version); ServerHttpRequest modifiedRequest = request.mutate() .header("X-Version", targetVersion) .build(); return chain.filter(exchange.mutate().request(modifiedRequest).build()); } private String getUserId(ServerHttpRequest request) { return request.getHeaders().getFirst("X-User-Id"); } private String getVersion(ServerHttpRequest request) { return request.getHeaders().getFirst("X-Version"); } }
@Service public class GrayReleaseService { @Autowired private RedisTemplate<String, String> redisTemplate;
public String getTargetVersion(String serviceName, String userId, String requestVersion) { if (requestVersion != null) { return requestVersion; } if (isInWhitelist(serviceName, userId)) { return "v2"; } double grayRatio = getGrayRatio(serviceName); if (shouldRouteToGray(userId, grayRatio)) { return "v2"; } return "v1"; }
private boolean isInWhitelist(String serviceName, String userId) { String key = "gray:whitelist:" + serviceName; return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, userId)); }
private double getGrayRatio(String serviceName) { String key = "gray:ratio:" + serviceName; String ratioStr = redisTemplate.opsForValue().get(key); return ratioStr == null ? 0.0 : Double.parseDouble(ratioStr); }
private boolean shouldRouteToGray(String userId, double ratio) { if (ratio <= 0) { return false; } if (ratio >= 1) { return true; } int hash = userId.hashCode(); return (hash % 100) < (ratio * 100); } }
|
4.1.3 Nginx实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| upstream order-service-v1 { server 192.168.1.10:8080; server 192.168.1.11:8080; }
upstream order-service-v2 { server 192.168.1.20:8080; server 192.168.1.21:8080; }
server { listen 80; server_name api.example.com; location /api/order/ { set $version "v1"; if ($cookie_version = "v2") { set $version "v2"; } if ($http_x_version = "v2") { set $version "v2"; } access_by_lua_block { local user_id = ngx.var.cookie_user_id or ngx.var.http_x_user_id if user_id then local redis = require "resty.redis" local red = redis:new() red:connect("127.0.0.1", 6379) local is_whitelist = red:sismember("gray:whitelist:order-service", user_id) if is_whitelist == 1 then ngx.var.version = "v2" end red:close() end } set_by_lua_block $gray_ratio { local redis = require "resty.redis" local red = redis:new() red:connect("127.0.0.1", 6379) local ratio = red:get("gray:ratio:order-service") or "0" red:close() return ratio } set_by_lua_block $user_hash { local user_id = ngx.var.cookie_user_id or ngx.var.http_x_user_id or "0" return tostring(math.abs(tonumber(string.sub(tostring(user_id), -2)) % 100)) } if ($user_hash < $gray_ratio) { set $version "v2"; } if ($version = "v2") { proxy_pass http://order-service-v2; } if ($version = "v1") { proxy_pass http://order-service-v1; } } }
|
4.2 基于服务注册中心的灰度发布
4.2.1 实现原理
基于服务注册中心的灰度发布:在服务注册时标记版本,消费方根据版本选择服务。
优势:
4.2.2 Nacos实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| @Service public class OrderService { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties;
@PostConstruct public void registerWithVersion() { Map<String, String> metadata = new HashMap<>(); metadata.put("version", "v2"); metadata.put("gray", "true"); nacosDiscoveryProperties.setMetadata(metadata); } }
@Configuration public class GrayReleaseRibbonConfig { @Bean public IRule grayReleaseRule() { return new GrayReleaseRule(); } }
public class GrayReleaseRule extends AbstractLoadBalancerRule { @Autowired private GrayReleaseService grayReleaseService; @Override public Server choose(Object key) { ILoadBalancer lb = getLoadBalancer(); List<Server> servers = lb.getReachableServers(); if (servers.isEmpty()) { return null; } String targetVersion = getTargetVersion(); List<Server> targetServers = servers.stream() .filter(server -> { String version = getServerVersion(server); return targetVersion.equals(version); }) .collect(Collectors.toList()); if (targetServers.isEmpty()) { return servers.get(0); } return targetServers.get(new Random().nextInt(targetServers.size())); } private String getTargetVersion() { RequestContext context = RequestContext.getCurrentContext(); String version = (String) context.get("X-Version"); return version != null ? version : "v1"; } private String getServerVersion(Server server) { if (server instanceof NacosServer) { NacosServer nacosServer = (NacosServer) server; return nacosServer.getMetadata().getOrDefault("version", "v1"); } return "v1"; } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } }
|
4.3 基于负载均衡的灰度发布
4.3.1 实现原理
基于负载均衡的灰度发布:在负载均衡层根据规则将流量分配到不同版本的服务。
优势:
- 灵活的路由规则
- 支持多种负载均衡算法
- 可以动态调整
4.3.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| @Service public class GrayReleaseLoadBalancer { @Autowired private GrayReleaseService grayReleaseService;
public ServiceInstance chooseService(List<ServiceInstance> instances, String serviceName) { String targetVersion = grayReleaseService.getTargetVersion(serviceName, getUserId(), getRequestVersion()); List<ServiceInstance> targetInstances = instances.stream() .filter(instance -> { String version = instance.getMetadata().getOrDefault("version", "v1"); return targetVersion.equals(version); }) .collect(Collectors.toList()); if (targetInstances.isEmpty()) { targetInstances = instances.stream() .filter(instance -> { String version = instance.getMetadata().getOrDefault("version", "v1"); return "v1".equals(version); }) .collect(Collectors.toList()); } return loadBalance(targetInstances); } private ServiceInstance loadBalance(List<ServiceInstance> instances) { int index = new Random().nextInt(instances.size()); return instances.get(index); } private String getUserId() { RequestContext context = RequestContext.getCurrentContext(); return (String) context.get("X-User-Id"); } private String getRequestVersion() { RequestContext context = RequestContext.getCurrentContext(); return (String) context.get("X-Version"); } }
|
5. 流量分配策略
5.1 按比例分配
5.1.1 实现原理
按比例分配:根据配置的比例将流量分配到新版本。
特点:
5.1.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| @Service public class RatioBasedGrayRelease { @Autowired private RedisTemplate<String, String> redisTemplate;
public boolean shouldRouteToGray(String serviceName, String userId) { double ratio = getGrayRatio(serviceName); if (ratio <= 0) { return false; } if (ratio >= 1) { return true; } int hash = Math.abs(userId.hashCode()); int bucket = hash % 100; return bucket < (ratio * 100); }
public void setGrayRatio(String serviceName, double ratio) { String key = "gray:ratio:" + serviceName; redisTemplate.opsForValue().set(key, String.valueOf(ratio)); } private double getGrayRatio(String serviceName) { String key = "gray:ratio:" + serviceName; String ratioStr = redisTemplate.opsForValue().get(key); return ratioStr == null ? 0.0 : Double.parseDouble(ratioStr); } }
|
5.2 按用户分配
5.2.1 实现原理
按用户分配:根据用户ID、用户标签等将特定用户路由到新版本。
特点:
5.2.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| @Service public class UserBasedGrayRelease { @Autowired private RedisTemplate<String, String> redisTemplate;
public boolean shouldRouteToGray(String serviceName, String userId) { if (isInWhitelist(serviceName, userId)) { return true; } if (isInBlacklist(serviceName, userId)) { return false; } if (hasGrayTag(serviceName, userId)) { return true; } return false; }
public void addToWhitelist(String serviceName, String userId) { String key = "gray:whitelist:" + serviceName; redisTemplate.opsForSet().add(key, userId); }
public void removeFromWhitelist(String serviceName, String userId) { String key = "gray:whitelist:" + serviceName; redisTemplate.opsForSet().remove(key, userId); }
private boolean isInWhitelist(String serviceName, String userId) { String key = "gray:whitelist:" + serviceName; return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, userId)); }
private boolean isInBlacklist(String serviceName, String userId) { String key = "gray:blacklist:" + serviceName; return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, userId)); }
private boolean hasGrayTag(String serviceName, String userId) { String key = "user:tags:" + userId; Set<String> tags = redisTemplate.opsForSet().members(key); if (tags == null) { return false; } return tags.contains("gray:" + serviceName); } }
|
5.3 按地域分配
5.3.1 实现原理
按地域分配:根据用户地域将特定地区的用户路由到新版本。
特点:
5.3.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| @Service public class RegionBasedGrayRelease { @Autowired private RedisTemplate<String, String> redisTemplate;
public boolean shouldRouteToGray(String serviceName, String region) { Set<String> grayRegions = getGrayRegions(serviceName); return grayRegions.contains(region); }
public void addGrayRegion(String serviceName, String region) { String key = "gray:regions:" + serviceName; redisTemplate.opsForSet().add(key, region); }
public void removeGrayRegion(String serviceName, String region) { String key = "gray:regions:" + serviceName; redisTemplate.opsForSet().remove(key, region); }
private Set<String> getGrayRegions(String serviceName) { String key = "gray:regions:" + serviceName; Set<String> regions = redisTemplate.opsForSet().members(key); return regions != null ? regions : Collections.emptySet(); } }
|
6. 版本管理
6.1 版本标识
6.1.1 版本号规则
版本号规则:
- 语义化版本:主版本号.次版本号.修订号(如:1.2.3)
- 时间版本:基于时间戳(如:20240428.001)
- Git版本:基于Git Commit Hash(如:abc1234)
6.1.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Service public class VersionService {
public String getCurrentVersion() { return System.getProperty("app.version", "1.0.0"); }
public int compareVersion(String version1, String version2) { String[] v1Parts = version1.split("\\."); String[] v2Parts = version2.split("\\."); int maxLength = Math.max(v1Parts.length, v2Parts.length); for (int i = 0; i < maxLength; i++) { int v1Part = i < v1Parts.length ? Integer.parseInt(v1Parts[i]) : 0; int v2Part = i < v2Parts.length ? Integer.parseInt(v2Parts[i]) : 0; if (v1Part < v2Part) { return -1; } else if (v1Part > v2Part) { return 1; } } return 0; } }
|
6.2 版本路由
6.2.1 路由规则
路由规则:
- 精确匹配:版本号完全匹配
- 前缀匹配:版本号前缀匹配
- 范围匹配:版本号范围匹配
6.2.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| @Service public class VersionRouter { @Autowired private VersionService versionService;
public String route(String serviceName, String requestVersion) { if (requestVersion != null) { return requestVersion; } String defaultVersion = getDefaultVersion(serviceName); String targetVersion = applyRoutingRules(serviceName, defaultVersion); return targetVersion; }
private String applyRoutingRules(String serviceName, String defaultVersion) { if (isGrayEnabled(serviceName)) { return getGrayVersion(serviceName); } if (isVersionCompatible(serviceName, defaultVersion)) { return defaultVersion; } return defaultVersion; } private String getDefaultVersion(String serviceName) { return "v1"; } private boolean isGrayEnabled(String serviceName) { return true; } private String getGrayVersion(String serviceName) { return "v2"; } private boolean isVersionCompatible(String serviceName, String version) { return true; } }
|
6.3 版本回滚
6.3.1 回滚策略
回滚策略:
- 快速回滚:立即切换回旧版本
- 逐步回滚:逐步减少新版本流量
- 数据回滚:回滚数据变更
6.3.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| @Service public class VersionRollbackService { @Autowired private RedisTemplate<String, String> redisTemplate;
public void quickRollback(String serviceName) { setGrayRatio(serviceName, 0.0); clearWhitelist(serviceName); sendRollbackNotification(serviceName); log.info("Quick rollback completed: service={}", serviceName); }
public void gradualRollback(String serviceName) { double currentRatio = getGrayRatio(serviceName); double[] steps = {0.25, 0.10, 0.05, 0.0}; for (double step : steps) { setGrayRatio(serviceName, step); log.info("Gradual rollback: service={}, ratio={}", serviceName, step); try { Thread.sleep(60000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } private void setGrayRatio(String serviceName, double ratio) { String key = "gray:ratio:" + serviceName; redisTemplate.opsForValue().set(key, String.valueOf(ratio)); } private double getGrayRatio(String serviceName) { String key = "gray:ratio:" + serviceName; String ratioStr = redisTemplate.opsForValue().get(key); return ratioStr == null ? 0.0 : Double.parseDouble(ratioStr); } private void clearWhitelist(String serviceName) { String key = "gray:whitelist:" + serviceName; redisTemplate.delete(key); } private void sendRollbackNotification(String serviceName) { alertService.sendAlert("版本回滚", serviceName); } }
|
7. 监控告警
7.1 发布监控
7.1.1 监控指标
监控指标:
- 错误率:新版本的错误率
- 响应时间:新版本的响应时间
- QPS:新版本的QPS
- 资源使用:CPU、内存使用率
7.1.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| @Component public class GrayReleaseMonitor { @Autowired private MeterRegistry meterRegistry;
public void recordRequest(String serviceName, String version, boolean success, long duration) { meterRegistry.counter("gray.request", "service", serviceName, "version", version, "status", success ? "success" : "error") .increment(); meterRegistry.timer("gray.response_time", "service", serviceName, "version", version) .record(duration, TimeUnit.MILLISECONDS); }
public boolean shouldRollback(String serviceName, String version) { double errorRate = getErrorRate(serviceName, version); if (errorRate > 0.05) { return true; } double avgResponseTime = getAvgResponseTime(serviceName, version); double baselineResponseTime = getBaselineResponseTime(serviceName); if (avgResponseTime > baselineResponseTime * 2) { return true; } return false; } private double getErrorRate(String serviceName, String version) { Counter errorCounter = meterRegistry.counter("gray.request", "service", serviceName, "version", version, "status", "error"); Counter totalCounter = meterRegistry.counter("gray.request", "service", serviceName, "version", version); if (totalCounter.count() == 0) { return 0.0; } return errorCounter.count() / totalCounter.count(); } private double getAvgResponseTime(String serviceName, String version) { Timer timer = meterRegistry.timer("gray.response_time", "service", serviceName, "version", version); return timer.mean(TimeUnit.MILLISECONDS); } private double getBaselineResponseTime(String serviceName) { Timer timer = meterRegistry.timer("gray.response_time", "service", serviceName, "version", "v1"); return timer.mean(TimeUnit.MILLISECONDS); } }
|
7.2 告警机制
7.2.1 告警规则
告警规则:
- 错误率告警:错误率超过阈值
- 响应时间告警:响应时间超过阈值
- 资源告警:CPU、内存使用率超过阈值
7.2.2 实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| @Component public class GrayReleaseAlert { @Autowired private AlertService alertService; @Autowired private GrayReleaseMonitor monitor;
@Scheduled(fixedDelay = 60000) public void checkAndAlert() { List<String> grayServices = getGrayServices(); for (String serviceName : grayServices) { String version = getGrayVersion(serviceName); double errorRate = monitor.getErrorRate(serviceName, version); if (errorRate > 0.05) { alertService.sendAlert("灰度发布错误率过高", String.format("服务:%s,版本:%s,错误率:%.2f%%", serviceName, version, errorRate * 100)); } double avgResponseTime = monitor.getAvgResponseTime(serviceName, version); double baselineResponseTime = monitor.getBaselineResponseTime(serviceName); if (avgResponseTime > baselineResponseTime * 2) { alertService.sendAlert("灰度发布响应时间过长", String.format("服务:%s,版本:%s,响应时间:%.2fms,基线:%.2fms", serviceName, version, avgResponseTime, baselineResponseTime)); } if (monitor.shouldRollback(serviceName, version)) { alertService.sendAlert("灰度发布需要回滚", String.format("服务:%s,版本:%s,建议立即回滚", serviceName, version)); } } } private List<String> getGrayServices() { return Arrays.asList("order-service", "payment-service"); } private String getGrayVersion(String serviceName) { return "v2"; } }
|
8. 实战案例
8.1 案例:订单服务灰度发布
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| @RestController @RequestMapping("/api/order") public class OrderController { @Autowired private OrderService orderService; @Autowired private GrayReleaseService grayReleaseService;
@PostMapping("/create") public Result<Order> createOrder(@RequestBody OrderRequest request, HttpServletRequest httpRequest) { String userId = getUserId(httpRequest); boolean isGray = grayReleaseService.shouldRouteToGray("order-service", userId); if (isGray) { return Result.success(orderService.createOrderV2(request)); } else { return Result.success(orderService.createOrderV1(request)); } } private String getUserId(HttpServletRequest request) { String userId = request.getHeader("X-User-Id"); if (userId == null) { userId = getCookieValue(request, "user_id"); } return userId != null ? userId : "anonymous"; } private String getCookieValue(HttpServletRequest request, String name) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (name.equals(cookie.getName())) { return cookie.getValue(); } } } return null; } }
|
9. 总结
9.1 核心要点
- 灰度发布:按比例逐步发布新版本,降低发布风险
- 金丝雀发布:先发布少量实例,验证后逐步扩大
- 实现方式:基于网关、基于服务注册中心、基于负载均衡
- 流量分配:按比例、按用户、按地域等分配策略
- 版本管理:版本标识、版本路由、版本回滚
- 监控告警:发布监控、指标收集、告警机制
9.2 关键理解
- 灰度发布:适合大规模系统,需要逐步验证
- 金丝雀发布:适合关键系统,需要最小风险
- 流量分配:根据业务特点选择合适的分配策略
- 监控告警:实时监控,及时发现问题并回滚
9.3 最佳实践
- 逐步发布:从10% → 50% → 100%逐步扩大
- 监控指标:监控错误率、响应时间、资源使用等
- 快速回滚:发现问题后立即回滚
- 白名单机制:内测用户使用白名单
- 版本兼容:保证新版本向后兼容
相关文章: