从0到1学SpringCloud——17 gateway nacos元数据实现灰度路由

目录

一、前言

二、效果演示

三、代码演示

1、网关配置

2、灰度服务配置

3、过滤器


一、前言

pom依赖版本信息:

<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version>

在当前版本的springcloud中,部分 reactive 包下的类已经弃用。

所以,对于有代码洁癖喜好的看起来会非常不爽,尤其是一个方法中有多个,如下图:

 

 虽然可以使用@SuppressWarnings("deprecation") 注解来使其显示正常,心里还是有阴影。

二、效果演示

上图中演示了根据不同版本进行路由, 代码示例较多,本篇文章重点在于去掉了之前被spring弃用的部分,部分代码进行改造。

三、代码演示

1、网关配置

链接nacos的配置及完整pom依赖信息可以参考之前的文章。

application.properties 灰度路由配置:

#服务端口
server.port=9999
#服务名称
spring.application.name=zhufeng-gateway-loadbalancer
# 灰度测试
spring.cloud.gateway.routes[0].id=zhufeng-gray
spring.cloud.gateway.routes[0].uri=grayLb://zhufeng-gray
spring.cloud.gateway.routes[0].predicates[0]=Path=/gray/version

2、灰度服务配置

新建两个用于灰度路由的微服务,zhufeng-gray-higher、zhufeng-gray-lower。

zhufeng-gray-higher:

application.properties配置:

#服务端口
server.port=8101
#服务名称
spring.application.name=zhufeng-gray

bootstrap.properties配置:

#nacos注册中心地址
spring.cloud.nacos.discovery.server-addr=10.211.55.9:8848,10.211.55.10:8848,10.211.55.11:8848
#nacos元数据配置
spring.cloud.nacos.discovery.metadata.version=higher
spring.cloud.nacos.discovery.metadata.weight=30
#nacos配置中心地址
spring.cloud.nacos.config.server-addr=${spring.cloud.nacos.discovery.server-addr}

controller:

@Slf4j
@RestController
@RequestMapping("gray")
public class HigherController {

    @RequestMapping("version")
    public String version() {
        log.info("访问higher版本...");
        return "这里是higher版本";
    }
}

zhufeng-gray-lower:

application.properties配置:

#服务端口
server.port=8102
#服务名称
spring.application.name=zhufeng-gray

bootstrap.properties配置:

#nacos注册中心地址
spring.cloud.nacos.discovery.server-addr=10.211.55.9:8848,10.211.55.10:8848,10.211.55.11:8848
#nacos元数据配置
spring.cloud.nacos.discovery.metadata.version=lower
spring.cloud.nacos.discovery.metadata.weight=60
#nacos配置中心地址
spring.cloud.nacos.config.server-addr=${spring.cloud.nacos.discovery.server-addr}

3、过滤器

@Slf4j
@Configuration
public class GrayGatewayReactiveLoadBalancerClientAutoConfiguration {
    public GrayGatewayReactiveLoadBalancerClientAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean({GrayReactiveLoadBalancerClientFilter.class})
    public GrayReactiveLoadBalancerClientFilter grayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
        log.info("初始化完成....");
        return new GrayReactiveLoadBalancerClientFilter(clientFactory, properties);
    }
}
/**
 * @ClassName: GrayLoadBalancer
 * @Description TODO
 * @author 月夜烛峰
 * @date 2022/10/8 15:35
 */
@Slf4j
public class GrayLoadBalancer  {

    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private  String serviceId;

    public GrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }


    public Mono<Response<ServiceInstance>> choose(Request request) {
        HttpHeaders headers = (HttpHeaders) request.getContext();
        if (this.serviceInstanceListSupplierProvider != null) {
            ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
            return ((Flux)supplier.get()).next().map(list->getInstanceResponse((List<ServiceInstance>)list,headers));
        }
        return null;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances,HttpHeaders headers) {
        if (instances.isEmpty()) {
            log.info("实例为空....");
            return getServiceInstanceEmptyResponse();
        } else {
            //return getServiceInstanceResponseWithWeight(instances);
            return getServiceInstanceResponseByVersion(instances, headers);
        }
    }



    /**
     * 根据版本进行分发
     * @param instances
     * @param headers
     * @return
     */
    private Response<ServiceInstance> getServiceInstanceResponseByVersion(List<ServiceInstance> instances, HttpHeaders headers) {
        String versionNo = headers.getFirst("version");
        log.info("查看版本:{}",versionNo);
        Map<String,String> versionMap = new HashMap<>(16);
        versionMap.put("version",versionNo);
        final Set<Map.Entry<String,String>> attributes =
                Collections.unmodifiableSet(versionMap.entrySet());
        ServiceInstance serviceInstance = null;
        for (ServiceInstance instance : instances) {
            log.info("开始获取实例instances...{}", JSONObject.toJSONString(instances));
            Map<String,String> metadata = instance.getMetadata();
            if(metadata.entrySet().containsAll(attributes)){
                log.info("开始获取实例...");
                serviceInstance = instance;
                break;
            }
        }

        if(ObjectUtils.isEmpty(serviceInstance)){
            log.info("实例为空...");
            return getServiceInstanceEmptyResponse();
        }
        return new DefaultResponse(serviceInstance);
    }

    /**
     *
     * 根据在nacos中配置的权重值,进行分发
     * @param instances
     *
     * @return
     */
    private Response<ServiceInstance> getServiceInstanceResponseWithWeight(List<ServiceInstance> instances) {

        Map<ServiceInstance,Integer> weightMap = new HashMap<>();
        for (ServiceInstance instance : instances) {
            log.info("获取所有实例:{}",JSONObject.toJSONString(instance));
            Map<String,String> metadata = instance.getMetadata();
            log.info("版本信息筛选:{} -->weight:",metadata.get("version"),metadata.get("weight"));
            if(metadata.containsKey("weight")){
                weightMap.put(instance,Integer.valueOf(metadata.get("weight")));
            }
        }
        WeightMeta<ServiceInstance> weightMeta = WeightRandomUtils.buildWeightMeta(weightMap);
        if(ObjectUtils.isEmpty(weightMeta)){
            return getServiceInstanceEmptyResponse();
        }
        ServiceInstance serviceInstance = weightMeta.random();
        if(ObjectUtils.isEmpty(serviceInstance)){
            return getServiceInstanceEmptyResponse();
        }
        log.info(serviceInstance.getMetadata().get("version"));
        return new DefaultResponse(serviceInstance);
    }

    private Response<ServiceInstance> getServiceInstanceEmptyResponse() {
        log.info("No servers available for service: {}",this.serviceId);
        return new EmptyResponse();
    }
}
/**
 * @ClassName: GrayReactiveLoadBalancerClientFilter
 * @Description TODO
 * @author 月夜烛峰
 * @date 2022/10/8 15:40
 */
@Slf4j
public class GrayReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
    /**读取网关请求报文体缓存key*/
    public static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";

    private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
    private final LoadBalancerClientFactory clientFactory;
    private LoadBalancerProperties properties;

    public GrayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
        this.clientFactory = clientFactory;
        this.properties = properties;
    }

    @Override
    public int getOrder() {
        return LOAD_BALANCER_CLIENT_FILTER_ORDER;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
        log.info("请求报文体:{}",cachedBody);
        String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
        if (url != null && ("grayLb".equals(url.getScheme()) || "grayLb".equals(schemePrefix)|| "lb1".equals(url.getScheme()))) {
            ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
            log.info("负载均衡选择:{}", ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);

            return this.choose(exchange).doOnNext((response) -> {
                if (!response.hasServer()) {
                    throw NotFoundException.create(this.properties.isUse404() , "Unable to find instance for " + url.getHost());
                } else {
                    URI uri = exchange.getRequest().getURI();
                    String overrideScheme = null;
                    if (schemePrefix != null) {
                        overrideScheme = url.getScheme();
                    }

                    DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance((ServiceInstance)response.getServer(), overrideScheme);
                    URI requestUrl = this.reconstructURI(serviceInstance, uri);
                    log.info("LoadBalancerClientFilter url chosen: {}" , requestUrl);

                    exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
                }
            }).then(chain.filter(exchange));
        } else {
            return chain.filter(exchange);
        }
    }

    protected URI reconstructURI(ServiceInstance serviceInstance, URI original) {
        return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
    }

    private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
        URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        GrayLoadBalancer loadBalancer = new GrayLoadBalancer(clientFactory.getLazyProvider(uri.getHost(), ServiceInstanceListSupplier.class), uri.getHost());
        if (loadBalancer == null) {
            throw new NotFoundException("No loadbalancer available for " + uri.getHost());
        } else {
            return loadBalancer.choose(this.createRequest(exchange));
        }
    }

    private Request createRequest(ServerWebExchange exchange) {
        HttpHeaders headers = exchange.getRequest().getHeaders();
        Request<HttpHeaders> request = new DefaultRequest<>(headers);
        return request;
    }
}
/**
 * @ClassName: WeightMeta
 * @Description TODO
 * @author 月夜烛峰
 * @date 2022/10/8 15:35
 */
 public class WeightMeta<T> {  
     private final Random ran = new Random();
     private final T[] nodes;  
     private final int[] weights;  
     private final int maxW;  
   
     public WeightMeta(T[] nodes, int[] weights) {  
         this.nodes = nodes;  
         this.weights = weights;  
         this.maxW = weights[weights.length - 1];  
     }  
   
     /** 
      * 该方法返回权重随机对象 
      * @return 
      */  
     public T random() {  
         int index = Arrays.binarySearch(weights, ran.nextInt(maxW) + 1);
         if (index < 0) {  
             index = -1 - index;  
         }  
         return nodes[index];  
     }  
   
     public T random(int ranInt) {  
         if (ranInt > maxW) {  
             ranInt = maxW;  
         } else if(ranInt < 0){  
             ranInt = 1;  
         } else {  
             ranInt ++;  
         }  
         int index = Arrays.binarySearch(weights, ranInt);  
         if (index < 0) {  
             index = -1 - index;  
         }  
         return nodes[index];  
     }  
   
     @Override  
     public String toString() {  
         StringBuilder l1 = new StringBuilder();  
         StringBuilder l2 = new StringBuilder("[random]\t");  
         StringBuilder l3 = new StringBuilder("[node]\t\t");  
         l1.append(this.getClass().getName()).append(":").append(this.hashCode()).append(":\n").append("[index]\t\t");  
         for (int i = 0; i < weights.length; i++) {  
             l1.append(i).append("\t");  
             l2.append(weights[i]).append("\t");  
             l3.append(nodes[i]).append("\t");  
         }  
         l1.append("\n");  
         l2.append("\n");  
         l3.append("\n");  
         return l1.append(l2).append(l3).toString();  
     }  
 }
/**
 * @ClassName: WeightRandomUtils
 * @Description TODO
 * @author 月夜烛峰
 * @date 2022/10/8 15:35
 */
public class WeightRandomUtils {
    public static <T> WeightMeta<T> buildWeightMeta(final Map<T, Integer> weightMap) {
        if(weightMap.isEmpty()){
            return null;
        }
        final int size = weightMap.size();  
        Object[] nodes = new Object[size];  
        int[] weights = new int[size];  
        int index = 0;  
        int weightAdder = 0;  
        for (Map.Entry<T, Integer> each : weightMap.entrySet()) {
            nodes[index] = each.getKey();  
            weights[index++] = (weightAdder = weightAdder + each.getValue());  
        }  
        return new WeightMeta<T>((T[]) nodes, weights);  
    }

   public static void main(String[] args) {
       Map<String,Integer> map = new HashMap<>();
       map.put("v1",1);
       map.put("v2",2);
       WeightMeta<String> nodes = WeightRandomUtils.buildWeightMeta(map);
       for(int i = 0; i < 10; i++){
          new Thread(()->{
              System.out.println(nodes.random());
          }).start();
       }

   }
}

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月夜烛峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值