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

相关推荐
计算机毕设指导61 小时前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
ExiFengs3 小时前
实际项目Java1.8流处理, Optional常见用法
java·开发语言·spring
wenyue11213 小时前
Revolutionize Your Kubernetes Experience with Easegress: Kubernetes Gateway API
容器·kubernetes·gateway
瓜牛_gn3 小时前
依赖注入注解
java·后端·spring
木子_lishk4 小时前
gateway 支持同时使用 https 和 http访问
https·gateway
一元咖啡4 小时前
SpringCloud Gateway转发请求到同一个服务的不同端口
spring·spring cloud·gateway
天天扭码5 小时前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
java亮小白19976 小时前
Spring循环依赖如何解决的?
java·后端·spring
跳跳的向阳花6 小时前
03-03、SpringCloud第三章,负载均衡Ribbon和Feign
spring cloud·ribbon·负载均衡