专栏首页冷冷Spring Cloud Gateway 扩展支持多版本控制及灰度发布
原创

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

灰度发布

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

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

实现方案

nginx + lua (openresty)

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

@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<String, String> 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的动态配置可以非常方便的实现灰度

总结

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 扩展Zuul实现敏捷开发的小小技巧

    你在开发工作的是否遇到这个问题,微服务模块划分过细,基础模块依赖的比较多? 比如你要进行微服务开发则需要启动以下基础模块

    冷冷
  • 扩展Zuul实现敏捷开发的小小技巧

    重写网关的转发规则,其实就是重写ribbon的路由规则,根据客户端不同的用户请求(可以根据入参不同区分)路由到对应的微服务上,所以对应的微服务上要打上标签。b...

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

    什么是灰度发布,概念请参考,我们来简单的通过下图来看下,通俗的讲: 为了保证服务升级过程的平滑过渡提高客户体验,会一部分用户 一部分用户递进更新,这样生产中会同...

    冷冷
  • SpringBoot入门建站全系列(九)文件上传功能与下载方式

    Spring对文件上传做了简单的封装,就是用MultipartFile这个对象去接收文件,当然有很多种写法,下面会一一介绍。

    品茗IT
  • i系统写excel

    这里写excel采用了POI工具。表报导出excel有二种,一种是xls,一种是xlsl。 定义一个接口 ReportsToExcel

    东风冷雪
  • Android APT 实例讲解

    APT(Annotation Processing Tool) 即注解处理器,是一种注解处理工具,用来在编译期扫描和处理注解,通过注解来生成 Java 文件。即...

    叶应是叶
  • 用netty 3的channelbuffer来重写序列化类

    我们都知道用java来序列化一个对象,需要用到ObjectOutputSteam来把对象写进一个字节流ByteOutputStream,然后把字节流转成字节数组...

    算法之名
  • Spring boot整合shiro权限管理

             #,授权,也就是访问控制,比如用于决定“谁”是否有权限访问“什么”;

    二十三年蝉
  • Springboot集成阿里云OSS

    用户5927264
  • IDEA 插件开发实战

    IntelliJ IDEA 是一款开发工具,提供很多插件功能,比如阿里规范插件(Alibaba Java Coding Guidelines),但是随着日常业务...

    CSDN技术头条

扫码关注云+社区

领取腾讯云代金券