springcloud gateway扩展支持多版本灰度

改造要求

需要在原有的调度策略中通过客户端header中的version进行1个服务多实例下进行二次分组,让指定的version在指定的版本实例下进行轮训调度。

需要改造的点

1.业务服务在发布到naocs中的元数据需要指定版本号

2.网关的调度策略中需要增加版本的区分

3.无版本的header。默认还继续使用轮训调度

4.使用jenkins进行发布服务的时候需要允许发布多个服务,

5.nacos中体现的形式是一个服务多个实例,只是元数据不同而已。

开始改造

在业务服务中的bootstrap.yml中指定version
gateway的改造

1.先定义好自己的LoadBanlancer

java 复制代码
public class EnhancedRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private final String serviceId;
    private final AtomicInteger position;

    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public EnhancedRoundRobinLoadBalancer(String serviceId,
                                          ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
        this.serviceId = serviceId;
        this.position = new AtomicInteger(new Random().nextInt(1000));
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next().map((serviceInstances) -> {
            return this.processInstanceResponse(request, supplier, serviceInstances);
        });

    }

    /**
     * copy自 RoundRobinLoadBalancer
     *
     * @param request
     * @param supplier
     * @param serviceInstances {@link  RoundRobinLoadBalancer}
     * @return
     */
    private Response<ServiceInstance> processInstanceResponse(Request request,
                                                              ServiceInstanceListSupplier supplier,
                                                              List<ServiceInstance> serviceInstances) {
//        log.info("-----------------------------------------------------");
//        log.info("获取到的实例个数:{}", serviceInstances.size());
        //根据header头携带version进行同一个服务下实例过滤,过滤后再使用roundRobinInstance轮训
        String headerVersion = getRequestHeader(request, "version");
//        log.info("获取到的version:{}", headerVersion);
        if (!ObjectUtils.isEmpty(headerVersion)){
            ArrayList<ServiceInstance> versionServiceInstances = new ArrayList<>();
            for (ServiceInstance serviceInstance : serviceInstances) {
                Map<String, String> metadata = serviceInstance.getMetadata();
                String version = metadata.get("version");
                if (!ObjectUtils.isEmpty(version) && headerVersion.equals(version)){
                    versionServiceInstances.add(serviceInstance);
                }
            }
            //带版本的实例不为空的情况下,使用版本实例进行轮训
            if (!ObjectUtils.isEmpty(versionServiceInstances)){
//                log.info("使用版本实例轮训,携带版本实例个数:{}",versionServiceInstances.size());
                Response<ServiceInstance> serviceInstanceResponse = getRoundRobinInstance(versionServiceInstances);
                if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
                    ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
                }
                return serviceInstanceResponse;
            }
        }



        Response<ServiceInstance> serviceInstanceResponse = getRoundRobinInstance(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }


    /**
     * 使用RoundRobin机制获取节点
     *
     * @param instances 实例
     * @return {@link Response }<{@link ServiceInstance }>
     * @author : baohui
     */
    private Response<ServiceInstance> getRoundRobinInstance(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("不存在可用的服务: " + serviceId);
            }
            return new EmptyResponse();
        } else if (instances.size() == 1) {
            return new DefaultResponse(instances.get(0));
        } else {
            // 每一次计数器都自动+1,实现轮询的效果
            int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
            ServiceInstance instance = instances.get(pos % instances.size());
            return new DefaultResponse(instance);
        }
    }

    private String getRequestHeader(Request request, String headerField) {
        HttpHeaders headers = ((RequestDataContext) request.getContext()).getClientRequest().getHeaders();
        //log.info("headers:{}", headers);
        return headers.getFirst(headerField);


    }
jenkins调整

调整jenkins发布pod的相关参数

  1. 在selector和template中增加version key

  2. 调整meta-data中的name生成机制。之所以需要调整name。是为了在k8s集群中可以让这两个服务共存。

nacos中的效果

写在最后

1.此方案是可以满足单独一个服务的的多版本共存,但是老的服务需要手动在集群中下线

2.涉及到多个服务跨服务调用的情况下需要根据跨服务的中间件单独处理。例如 dubbo的话需要调整dubbo调用的version参数。这样才能满足新旧服务的调用隔离。

3.涉及到http客户端调用需要中转到网关后并在请求参数中增加version才能达到隔离的效果

相关推荐
Auc243 小时前
OJ判题系统第4期之判题机模块架构——设计思路、实现步骤、代码实现(工厂模式、代理模式的实践)
java·spring cloud·log4j·mybatis·代理模式·工厂模式
有梦想的攻城狮3 小时前
spring中的@Lazy注解详解
java·后端·spring
大G哥5 小时前
实战演练:用 AWS Lambda 和 API Gateway 构建你的第一个 Serverless API
云原生·serverless·云计算·gateway·aws
zizisuo7 小时前
面试篇:Spring MVC
java·spring·mvc
-曾牛7 小时前
Spring AI 与 Hugging Face 深度集成:打造高效文本生成应用
java·人工智能·后端·spring·搜索引擎·springai·deepseek
计算机学姐8 小时前
基于SpringBoot的小区停车位管理系统
java·vue.js·spring boot·后端·mysql·spring·maven
说淑人8 小时前
Spring Cloud & 以Gateway实现限流(自定义返回内容)
java·spring cloud·gateway·限流
添砖Java中9 小时前
深入剖析缓存与数据库一致性:Java技术视角下的解决方案与实践
java·数据库·spring boot·spring·缓存·双写一致性
天上掉下来个程小白10 小时前
添加购物车-02.代码开发
java·服务器·前端·后端·spring·微信小程序·苍穹外卖
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧20 小时前
MyBatis快速入门——实操
java·spring boot·spring·intellij-idea·mybatis·intellij idea