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

灰度发布

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

8602648cf6341e72017c252f48afc38b.png

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

实现方案

nginx + lua (openresty)

2e429131b56284d2a06bebe2cc699205.png

138647f6437124dd243c6027ce6da760.png

Netflix Zuul

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

@Slf4j

public class MetadataCanaryRuleHandler 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 metadata = server.getInstanceInfo().getMetadata();

if (StrUtil.isBlank(metadata.get(SecurityConstants.VERSION))) {

log.debug("当前微服务{} 未配置版本直接路由");

return true;

}

if (metadata.get(SecurityConstants.VERSION).equals(targetVersion)) {

return true;

} else {

log.debug("当前微服务{} 版本为{},目标版本{} 匹配失败", server.getInstanceInfo().getAppName()

, metadata.get(SecurityConstants.VERSION), targetVersion);

return false;

}

}

};

}

}

维护请求中的版本号

public class RibbonVersionHolder {

private static final ThreadLocal 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 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 方法,把上下文请求传递到路由断言中即可,如下

@Override

protected 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 GrayMetadataAwarePredicate extends AbstractDiscoveryEnabledPredicate {

@Override

protected boolean apply(NacosServer server, HttpHeaders headers) {

PigxRibbonRuleProperties ribbonProperties = SpringContextHolder.getBean(PigxRibbonRuleProperties.class);

if (!ribbonProperties.isGrayEnabled()) {

log.debug("gray closed,GrayMetadataAwarePredicate return true");

return true;

}

final Map metadata = server.getMetadata();

String version = metadata.get(CommonConstants.VERSION);

// 判断Nacos服务是否有版本标签

if (StrUtil.isBlank(version)) {

log.debug("nacos server tag is blank ,GrayMetadataAwarePredicate return true");

return true;

}

// 判断请求中是否有版本

String target = headers.getFirst(CommonConstants.VERSION);

if (StrUtil.isBlank(target)) {

log.debug("request headers version is blank,GrayMetadataAwarePredicate return true");

return true;

}

log.debug("请求版本:{} ,当前服务版本:{}", target, version);

return target.equals(version);

}

}

整合nacos

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

e1622c868f3551dea8cb2c98dbf0d87c.png

总结

欢迎关注我们获得更多的好玩JavaEE 实践

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值