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

本文涉及的产品
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
简介: 灰度发布 什么是灰度发布,概念请参考,我们来简单的通过下图来看下,通俗的讲: 为了保证服务升级过程的平滑过渡提高客户体验,会一部分用户 一部分用户递进更新,这样生产中会同时出现多个版本的客户端,为了保证多个版本客户端的可用需要对应的多个版本的服务端版本。

灰度发布

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

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

实现方案

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

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
6月前
|
Java API Spring
Spring Boot中的API版本控制
Spring Boot中的API版本控制
|
6月前
|
负载均衡 Java Spring
Spring cloud gateway 如何在路由时进行负载均衡
Spring cloud gateway 如何在路由时进行负载均衡
699 15
|
6月前
|
Java Spring
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
146 3
|
6月前
|
Java 测试技术 UED
在Spring Boot中实现灰度发布的策略
在Spring Boot中实现灰度发布的策略
|
7月前
|
监控 Java 应用服务中间件
Spring Boot应用的部署与扩展
Spring Boot应用的部署与扩展
|
7月前
|
Java API 网络架构
Spring Cloud Gateway的高级配置与实践
Spring Cloud Gateway的高级配置与实践
|
6月前
|
缓存 监控 Java
通用快照方案问题之Spring Boot Admin的定义如何解决
通用快照方案问题之Spring Boot Admin的定义如何解决
78 0
|
6月前
|
Java 测试技术 UED
在Spring Boot中实现灰度发布的策略
在Spring Boot中实现灰度发布的策略
|
6月前
|
Java API 网络架构
Spring Boot与Spring Cloud Gateway的集成
Spring Boot与Spring Cloud Gateway的集成