gateway灰度发布(一)

本文介绍了一种灰度发布的负载均衡策略实现,通过自定义负载均衡过滤器和负载均衡器,实现了基于权重和服务版本的选择性负载均衡。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GrayGatewayReactiveLoadBalancerClientAutoConfiguration

package com.zy.platform.getway.configure;

import com.zy.platform.getway.filter.GrayReactiveLoadBalancerClientFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author javachen
 * @description GrayGatewayReactiveLoadBalancerClientAutoConfiguration
 */
@Configuration
public class GrayGatewayReactiveLoadBalancerClientAutoConfiguration {

    public GrayGatewayReactiveLoadBalancerClientAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean({GrayReactiveLoadBalancerClientFilter.class})
    public GrayReactiveLoadBalancerClientFilter grayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, LoadBalancerProperties properties) {
        return new GrayReactiveLoadBalancerClientFilter(clientFactory, properties);
    }

}
GrayReactiveLoadBalancerClientFilter
package com.zy.platform.getway.filter;

import com.zy.platform.getway.weight.loadbalancer.GrayLoadBalancer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
import org.springframework.cloud.client.loadbalancer.reactive.DefaultRequest;
import org.springframework.cloud.client.loadbalancer.reactive.Request;
import org.springframework.cloud.client.loadbalancer.reactive.Response;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter;
import org.springframework.cloud.gateway.support.DelegatingServiceInstance;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;

/**
 * @author javachen
 * @description GrayReactiveLoadBalancerClientFilter
 */
public class GrayReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {

    private static final Log log = LogFactory.getLog(ReactiveLoadBalancerClientFilter.class);
    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);
        String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
        if (url != null && ("grayLb".equals(url.getScheme()) || "grayLb".equals(schemePrefix))) {
            ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
            if (log.isTraceEnabled()) {
                log.trace(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);
                    if (log.isTraceEnabled()) {
                        log.trace("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 = (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;
    }

}

GrayLoadBalancer

package com.zy.platform.getway.weight.loadbalancer;

import com.zy.platform.getway.weight.model.WeightMeta;
import com.zy.platform.getway.weight.util.WeightRandomUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.reactive.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.reactive.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.reactive.Request;
import org.springframework.cloud.client.loadbalancer.reactive.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.*;

/**
 * @author javachen
 * @description GrayReactiveLoadBalancerClientFilter
 */
public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private static final Log log = LogFactory.getLog(GrayLoadBalancer.class);
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private  String serviceId;

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

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        HttpHeaders headers = (HttpHeaders) request.getContext();
        if (this.serviceInstanceListSupplierProvider != null) {
            ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)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()) {
            return getServiceInstanceEmptyResponse();
        } else {
            return getServiceInstanceResponseWithWeight(instances);
        }
    }

    /**
     * 根据版本进行分发
     * @param instances
     * @param headers
     * @return
     */
    private Response<ServiceInstance> getServiceInstanceResponseByVersion(List<ServiceInstance> instances, HttpHeaders headers) {
        String versionNo = headers.getFirst("version");
        //System.out.println(versionNo);
        Map<String,String> versionMap = new HashMap<>();
        versionMap.put("version",versionNo);
        final Set<Map.Entry<String,String>> attributes =
                Collections.unmodifiableSet(versionMap.entrySet());
        ServiceInstance serviceInstance = null;
        for (ServiceInstance instance : instances) {
            Map<String,String> metadata = instance.getMetadata();
            if(metadata.entrySet().containsAll(attributes)){
                serviceInstance = instance;
                break;
            }
        }

        if(ObjectUtils.isEmpty(serviceInstance)){
            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) {
            Map<String,String> metadata = instance.getMetadata();
            //System.out.println(metadata.get("version")+"-->weight:"+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();
        }
        System.out.println(serviceInstance.getMetadata().get("version"));
        return new DefaultResponse(serviceInstance);
    }

    private Response<ServiceInstance> getServiceInstanceEmptyResponse() {
        log.warn("No servers available for service: " + this.serviceId);
        return new EmptyResponse();
    }
}

WeightMeta

package com.zy.platform.getway.weight.model;

import java.util.Arrays;
import java.util.Random;

/**
 * @author javachen
 * @description 权重元数据对象
 */
 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();  
     }  
 }
WeightRandomUtils
package com.zy.platform.getway.weight.util;

import com.zy.platform.getway.weight.model.WeightMeta;

import java.util.HashMap;
import java.util.Map;

/**
 *  权重算法取自:@see https://www.ctolib.com/topics-61571.html
 *
  * 随机工具类 
  * 
  * 使用权重的集合Map构建随机元数据对象 
  * 
  * 比如: 
  * 我们有3个url地址,他们的权重分别为1,2,3现在我们利用RandomUtil来根据权重随机获取url: 
  * 
  * <p><blockquote><pre> 
  * 
  * map.put(url1, 1); 
  * map.put(url2, 2); 
  * map.put(url3, 3); 
  * RandomMeta<String, Integer> md = WeightRandomUtils.buildWeightMeta(map);
  * String weightRandomUrl = md.random(); 
  * 
  * </pre></blockquote><p> 
  * 
  *
  */  
 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();
        }

    }
 }

bootstrap.yml

server:
  port: 8080
# 配置输出日志

spring:
  application:
    # 应用名称
    name: platform-getway
  servlet:
    multipart:
      enabled: true #开启文件上传
      max-file-size: 500MB
      max-request-size: 500MB
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        dashboard: 192.168.196.128:8858
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
          lower-case-service-id: true
      routes:

        - id: platform-upload
          uri: lb://platform-upload
          predicates:
            - Path=/platform-upload/**
          filters:
            - StripPrefix=1 #代表去除两个前缀/api-user


        - id: platform-admin-server
          uri: grayLb://platform-admin-server
          predicates:
            - Path=/admin-server/**
          filters:
            - StripPrefix=1 #代表去除两个前缀/api-user


#        - id: url-proxy-1
#          uri: https://www.jd.com/
#          predicates:
#            - Path=/jd

项目结构

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

javachen__

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

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

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

打赏作者

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

抵扣说明:

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

余额充值