• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

SpringCloudGateWay自定义灰度发布

武飞扬头像
等不见天亮等时光i
帮助1

我的个人网站:等不见天亮等时光

灰度发布:在进行内部版本发布时希望有一部分用户能体验新的功能进行打标签的方式进行分区访问;

  • 基于SpringCloudGateWay的灰度发布实践
    • 在gateway中进行服务路由是通过Ribbon进行负载的,SpringCloudGateWay使用到的路由策略有两种分别是RandomLoadBalancer和RoundRobinLoadBalancer两个类进行实现的,我们可以参考上述两个负载均衡实现类,在进行路由选择策略中,将灰度服务和非灰度服务进行区分,进行路由;
    • 整理流程如下
      1. 首先,我们需要给服务器打标签,来区分灰度服务与非灰度服务;
      2. 给用户打标签来标注当前用户是内测灰度用户;
      3. 网关进行负载均衡时判断用户请求信息是否携带灰度参数,若携带参数则去匹配对应的灰度服务,不存在则走正常的服务;
      4. 内部微服务调用,灰度服务应该调用灰度服务,还要考虑到部分灰度服务需要进行指定调用,这部分作为补充,实现思路为重写@FeignClient的负载均衡策略,从网关转发灰度服务时增加灰度标签的请求头,在具体的业务服务模块对其进行拦截判断是否携带灰度标签,若存在则需要与3中一样获取调用方服务的server列表通过元数据进行过滤,最终进行指向性的调用,这部分后续会增加代码实现
  • 首先是第一项,给应用服务打标签,我们使用的服务注册中心时eureka,eureka有一个metadata-map元数据域,我们可以在服务启动时将当前服务存储一个Key,Value的键值对,如下所示,我们配置了一个GRAY_META,版本是1.0.0;
    学新通
  • 第二项给用户打标签,这个便签比较动态,可以在内部服务系统给用户直接加一个标签,通过网关去查询,也可以在返回用户信息时,增加一个Header进行区分,如下所示
    学新通
  • 第三项,网关判断用户信息与服务器信息进行比对,那么我们就无法使用默认的负载均衡操作了,需要去自定义一个负载均衡,首先已知负载均衡都实现的是ReactorServiceInstanceLoadBalancer类进行负载均衡,那么我们也可以去实现这个类去自定义一个我们自己的负载均衡,代码如下
@Slf4j
@Component
public class GrayFilter implements GlobalFilter, Ordered {

    private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;

    private final LoadBalancerClientFactory clientFactory;

    public GrayFilter(LoadBalancerClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("<-----------------------------[网关-路由选择]:路由选择开始----------------------------->");
        URI url = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        String schemePrefix = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
        ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
        if (log.isTraceEnabled()) {
            log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName()   " url before: "   url);
        }
        return this.choose(exchange).doOnNext((response) -> {
            if (!response.hasServer()) {
                assert url != null;
                throw NotFoundException.create(true, "Unable to find instance for "   url.getHost());
            } else {
                URI uri = exchange.getRequest().getURI();
                String overrideScheme = null;
                if (schemePrefix != null) {
                    assert url != null;
                    overrideScheme = url.getScheme();
                }
                DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(response.getServer(), overrideScheme);
                URI requestUrl = this.reconstructURI(serviceInstance, uri);
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
            }
        }).then(chain.filter(exchange));
    }


    private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
        URI uri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        //获取可用路由信息
        assert uri != null;
        GrayRoundRobinLoadBalancer loadBalancer = new GrayRoundRobinLoadBalancer(clientFactory.getLazyProvider(uri.getHost(), ServiceInstanceListSupplier.class), uri.getHost());
        return loadBalancer.choose(this.createRequest(exchange));
    }

    private Request createRequest(ServerWebExchange exchange) {
        HttpHeaders headers = exchange.getRequest().getHeaders();
        return new DefaultRequest<>(headers);
    }

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

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

}
public class GrayRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
	
	//对应用户请求的Key
	private static final String GaryKey = "GATE-GRAY-VERSION";
	//对应灰度应用metadata中的Key
	private static final String GRAY_META = "GRAY_META";
    private Logger log = LoggerFactory.getLogger(GrayRoundRobinLoadBalancer.class);


    private final AtomicInteger position;
    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private final String serviceId;

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

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

    private Response<ServiceInstance> processInstanceResponse(List<ServiceInstance> serviceInstances,HttpHeaders headers) {
        Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances,headers);
        if (serviceInstanceResponse instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback)serviceInstanceResponse).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    /**
     * 获取可用实例
     * @param instances 匹配serverId的应用列表
     * @param headers 请求头
     * @return 可用server
     */
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, HttpHeaders headers) {
        String version = headers.getFirst(GaryKey);
        //存放灰度server
        List<ServiceInstance> grayInstanceServer =new ArrayList<>();
        //非灰度server
        List<ServiceInstance> defaultInstanceServer =new ArrayList<>();
        for (ServiceInstance serviceInstance : instances) {
            if (StringUtils.isNotBlank(version) && version.equals(serviceInstance.getMetadata().get(GRAY_META))) {
                grayInstanceServer.add(serviceInstance);
            } else {
                Map<String, String> metadata = serviceInstance.getMetadata();
                //meta是空的也加入
                if (CollectionUtils.isEmpty(serviceInstance.getMetadata())){
                    defaultInstanceServer.add(serviceInstance);
                }else{
                    //判断是否有版本号,有的话剔除,没有的话就加入到默认server中
                    if (StringUtils.isBlank(metadata.get(GateWayConstant.GATEWAY_GRAY_META_KEY))){
                        defaultInstanceServer.add(serviceInstance);
                    }
                }
            }
        }
        int pos = Math.abs(this.position.incrementAndGet());
        ServiceInstance instance;
        if (StringUtils.isNotBlank(version)){
            if (grayInstanceServer.isEmpty()) {
                if (log.isWarnEnabled()) {
                    log.warn("请求灰度应用[{}]-对应版本[{}] 无可用路由",serviceId,version);
                }
                return new EmptyResponse();
            }
            instance=grayInstanceServer.get(pos % grayInstanceServer.size());
            log.info("<-----------------------------[网关-路由选择]:携带版本号为:[{}],请求地址[{}]----------------------------->", version,instance.getUri());
        }else {
            if (defaultInstanceServer.isEmpty()) {
                if (log.isWarnEnabled()) {
                    log.warn("请求应用[{}]无可用路由",serviceId);
                }
                return new EmptyResponse();
            }
            instance=defaultInstanceServer.get(pos % defaultInstanceServer.size());
            log.info("<-----------------------------[网关-路由选择]:[{}]默认路由,请求地址:[{}]----------------------------->",serviceId,instance.getUri());
        }
        return new DefaultResponse(instance);
    }
  • 以上三步我们就可以来对灰度应用与灰度用户匹配来达到我们灰度测试的目的
  • 文章中写的比较粗暴简单,可以考虑使用配置中心像Apollo或者nacos来进行动态化的配置和转换,包括服务器的灰度版本也可以考虑在网关配置router时配置在metadata中

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhggbbih
系列文章
更多 icon
同类精品
更多 icon
继续加载