Spring Cloud Gateway 扩展支持多版本控制及灰度发布

灰度发布

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

所谓灰度发布,即某个服务存在多个实例时,并且实例版本间的版本并不一致,通过

实现方案

nginx + lua (openresty)

Netflix Zuul

只需要自定义ribbon 的断言即可,核心是通过TTL 获取上下请求header中的版本号

1@Slf4j 2public class MetadataCanaryRuleHandler extends ZoneAvoidanceRule { 3 4 @Override 5 public AbstractServerPredicate getPredicate() { 6 return new AbstractServerPredicate() { 7 @Override 8 public boolean apply(PredicateKey predicateKey) { 9 String targetVersion = RibbonVersionHolder.getContext(); 10 RibbonVersionHolder.clearContext(); 11 if (StrUtil.isBlank(targetVersion)) { 12 log.debug("客户端未配置目标版本直接路由"); 13 return true; 14 } 15 16 DiscoveryEnabledServer server = (DiscoveryEnabledServer) predicateKey.getServer(); 17 final Map<string, string> metadata = server.getInstanceInfo().getMetadata(); 18 if (StrUtil.isBlank(metadata.get(SecurityConstants.VERSION))) { 19 log.debug("当前微服务{} 未配置版本直接路由"); 20 return true; 21 } 22 23 if (metadata.get(SecurityConstants.VERSION).equals(targetVersion)) { 24 return true; 25 } else { 26 log.debug("当前微服务{} 版本为{},目标版本{} 匹配失败", server.getInstanceInfo().getAppName() 27 , metadata.get(SecurityConstants.VERSION), targetVersion); 28 return false; 29 } 30 } 31 }; 32 } 33}

维护请求中的版本号

1public class RibbonVersionHolder { 2 private static final ThreadLocal<string> context = new TransmittableThreadLocal&lt;&gt;(); 3 4 public static String getContext() { 5 return context.get(); 6 } 7 8 public static void setContext(String value) { 9 context.set(value); 10 } 11 12 public static void clearContext() { 13 context.remove(); 14 } 15}

Spring Cloud Gateway 中实现

第一反应,参考zuul 的实现,自定义断言,然后从上下中获取版本信息即可。但由于 spring cloud gateway 是基于webflux 的反应式编程,所以传统的TTL或者 RequestContextHolder 都不能正确的维护上下文请求。

先来看 spring clou的 gateway 默认的lb 策略实现 LoadBalancerClientFilter

1public class LoadBalancerClientFilter implements GlobalFilter, Ordered { 2 @Override 3 public int getOrder() { 4 return LOAD_BALANCER_CLIENT_FILTER_ORDER; 5 } 6 7 @Override 8 @SuppressWarnings("Duplicates") 9 public Mono<void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { 10 return chain.filter(exchange); 11 } 12 13 protected ServiceInstance choose(ServerWebExchange exchange) { 14 return loadBalancer.choose( 15 ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost()); 16 } 17}

我们只需要重写 choose 方法,把上下文请求传递到路由断言中即可,如下

1@Override 2protected ServiceInstance choose(ServerWebExchange exchange) { 3 HttpHeaders headers = exchange.getRequest().getHeaders(); 4 return loadBalancer.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost(), headers); 5}

然后在路由断言中通过 PredicateKey获取到即可

1public abstract class AbstractDiscoveryEnabledPredicate extends AbstractServerPredicate { 2 3 /** 4 * {@inheritDoc} 5 */ 6 @Override 7 public boolean apply(@Nullable PredicateKey input) { 8 return input != null 9 &amp;&amp; input.getServer() instanceof NacosServer 10 &amp;&amp; apply((NacosServer) input.getServer(), (HttpHeaders) input.getLoadBalancerKey()); 11 } 12}

最后根据版本来计算

1 public class GrayMetadataAwarePredicate extends AbstractDiscoveryEnabledPredicate { 2 3 @Override 4 protected boolean apply(NacosServer server, HttpHeaders headers) { 5 PigxRibbonRuleProperties ribbonProperties = SpringContextHolder.getBean(PigxRibbonRuleProperties.class); 6 7 if (!ribbonProperties.isGrayEnabled()) { 8 log.debug("gray closed,GrayMetadataAwarePredicate return true"); 9 return true; 10 } 11 12 final Map<string, string> metadata = server.getMetadata(); 13 String version = metadata.get(CommonConstants.VERSION); 14 // 判断Nacos服务是否有版本标签 15 if (StrUtil.isBlank(version)) { 16 log.debug("nacos server tag is blank ,GrayMetadataAwarePredicate return true"); 17 return true; 18 } 19 20 // 判断请求中是否有版本 21 String target = headers.getFirst(CommonConstants.VERSION); 22 if (StrUtil.isBlank(target)) { 23 log.debug("request headers version is blank,GrayMetadataAwarePredicate return true"); 24 return true; 25 } 26 27 log.debug("请求版本:{} ,当前服务版本:{}", target, version); 28 return target.equals(version); 29 } 30 31}

整合nacos

结合nacos的动态配置可以非常方便的实现灰度

总结

欢迎关注我们获得更多的好玩JavaEE 实践</string,></void></string></string,>

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Nepxion Discovery 5.5.0 发布

!(https://oscimg.oschina.net/oscnet/f81c043194ef4732880459d00c1a720e.png)发布日志功能更新:增加基于Opentracing调用链的支持,目前支持UberJaeger,实现在SpringCloudGateway、Zuul和服务上的灰度