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才能达到隔离的效果

相关推荐
HalvmånEver14 小时前
7.高并发内存池大页内存申请释放以及使用定长内存池脱离new
java·spring boot·spring
一直都在57216 小时前
Spring面经
java·后端·spring
xiaoye370816 小时前
如何在Spring中使用注解配置Bean的生命周期回调方法?
java·spring
闻哥16 小时前
深入Redis的RDB和AOF两种持久化方式以及AOF重写机制的分析
java·数据库·spring boot·redis·spring·缓存·面试
jgyzl16 小时前
2026.3.12 常见的缓存读写策略
java·后端·spring
ruanyongjing17 小时前
Spring TransactionTemplate 深入解析与高级用法
java·数据库·spring
xiaoye370817 小时前
Spring Bean 生命周期
java·spring
6+h17 小时前
【Spring】Bean的生命周期详解
java·python·spring
冬夜戏雪17 小时前
面经摘录(五)
java·后端·spring
人道领域17 小时前
苍穹外卖:菜品分页查询与删除功能(保姆级详解)
java·开发语言·数据库·后端·spring