灰度发布
什么是灰度发布,概念请参考,我们来简单的通过下图来看下,通俗的讲: 为了保证服务升级过程的平滑过渡提高客户体验,会一部分用户 一部分用户递进更新,这样生产中会同时出现多个版本的客户端,为了保证多个版本客户端的可用需要对应的多个版本的服务端版本。灰度发布就是通过一定策略保证 多个版本客户端、服务端间能够正确对应。 
所谓灰度发布,即某个服务存在多个实例时,并且实例版本间的版本并不一致,通过
实现方案
nginx + lua (openresty)


Netflix Zuul
只需要自定义ribbon 的断言即可,核心是通过TTL 获取上下请求header中的版本号
@Slf4jpublic class dataCanaryRuleHandler extends ZoneAvoidanceRule { @Override public AbstractServerPredicate getPredicate() { return new AbstractServerPredicate() { @Override public boolean apply(PredicateKey predicateKey) { String targetVersion = RibbonVersionHolder.getContext(); RibbonVersionHolder.clearContext(); if (StrUtil.isBlank(targetVersion)) { log.debug("客户端未配置目标版本直接路由"); return true; } DiscoveryEnabledServer server = (DiscoveryEnabledServer) predicateKey.getServer(); final Map<String, String> data = server.getInstanceInfo().get data(); if (StrUtil.isBlank( data.get(SecurityConstants.VERSION))) { log.debug("当前微服务{} 未配置版本直接路由"); return true; } if ( data.get(SecurityConstants.VERSION).equals(targetVersion)) { return true; } else { log.debug("当前微服务{} 版本为{},目标版本{} 匹配失败", server.getInstanceInfo().getAppName() , data.get(SecurityConstants.VERSION), targetVersion); return false; } } }; }}维护请求中的版本号
public class RibbonVersionHolder { private static final ThreadLocal<String> context = new TransmittableThreadLocal<>(); public static String getContext() { return context.get(); } public static void setContext(String value) { context.set(value); } public static void clearContext() { context.remove(); }}Spring Cloud Gateway 中实现
第一反应,参考zuul 的实现,自定义断言,然后从上下中获取版本信息即可。但由于 spring cloud gateway 是基于webflux 的反应式编程,所以传统的TTL或者 RequestContextHolder 都不能正确的维护上下文请求。
先来看 spring clou的 gateway 默认的lb 策略实现 LoadBalancerClientFilter
public class LoadBalancerClientFilter implements GlobalFilter, Ordered { @Override public int getOrder() { return LOAD_BALANCER_CLIENT_FILTER_ORDER; } @Override @SuppressWarnings("Duplicates") public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange); } protected ServiceInstance choose(ServerWebExchange exchange) { return loadBalancer.choose( ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost()); }}我们只需要重写 choose 方法,把上下文请求传递到路由断言中即可,如下
@Overrideprotected ServiceInstance choose(ServerWebExchange exchange) { HttpHeaders headers = exchange.getRequest().getHeaders(); return loadBalancer.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost(), headers);}然后在路由断言中通过 PredicateKey获取到即可
public abstract class AbstractDiscoveryEnabledPredicate extends AbstractServerPredicate { /** * {@inheritDoc} */ @Override public boolean apply(@Nullable PredicateKey input) { return input != null && input.getServer() instanceof NacosServer && apply((NacosServer) input.getServer(), (HttpHeaders) input.getLoadBalancerKey()); }}最后根据版本来计算
public class Gray dataAwarePredicate extends AbstractDiscoveryEnabledPredicate { @Override protected boolean apply(NacosServer server, HttpHeaders headers) { PigxRibbonRuleProperties ribbonProperties = SpringContextHolder.getBean(PigxRibbonRuleProperties.class); if (!ribbonProperties.isGrayEnabled()) { log.debug("gray closed,Gray dataAwarePredicate return true"); return true; } final Map<String, String> data = server.get data(); String version = data.get(CommonConstants.VERSION); // 判断Nacos服务是否有版本标签 if (StrUtil.isBlank(version)) { log.debug("nacos server tag is blank ,Gray dataAwarePredicate return true"); return true; } // 判断请求中是否有版本 String target = headers.getFirst(CommonConstants.VERSION); if (StrUtil.isBlank(target)) { log.debug("request headers version is blank,Gray dataAwarePredicate return true"); return true; } log.debug("请求版本:{} ,当前服务版本:{}", target, version); return target.equals(version); }}整合nacos
结合nacos的动态配置可以非常方便的实现灰度
继续阅读与本文标签相同的文章
上一篇 :
Java中Synchornize关键字原理
下一篇 :
Centos下kafka 单机配置部署详解
-
Python3入门(八)模块
2026-05-24栏目: 教程
-
RocketMQ问题排查思路
2026-05-24栏目: 教程
-
maxCompute(ODPS)问题排查思路
2026-05-24栏目: 教程
-
K8S实践 - Promethues从VM迁移K8S实录
2026-05-24栏目: 教程
-
借助URLOS快速安装MySQL 5.6
2026-05-24栏目: 教程
