网上查看现有的一些实现gateway灰度发布的博客,一般都是使用过滤器、拦截器等,过于复杂,而且不够灵活,索性自己研究一下gateway,发现可以通过 AbstractLoadBalancerRule 实现,下面是我实现的一套灵活一些的灰度发布策略。

首先交代下环境:jdk8、SpringBoot 2.3.6.RELEASE、SpringCloud Hoxton.SR9、AlibabaCloud 2.2.3 RELEASE。

gateway的配置文件如下(可以配置到nacos中):

# 服务灰度发布配置
server:
  gray:
    config:
      # 需要管理发布的服务server-id,当前为 producer 服务
      producer:
        # 需要灰度发布的版本
        version: 20210423
        # 请求进入灰度服务的概率 0~1,为 0 表示不进入灰度服务,1 则完全灰度
        rate: 0

spring:
  cloud:
    gateway:
      routes:
        - id: producer_server
          uri: lb://producer
          predicates:
            - Path=/producer/**
          filters:
            - StripPrefix=0

配置类:

package com.study.gateway.config;

import com.alibaba.nacos.common.utils.MapUtils;
import lombok.Data;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @author zhaochao
 * @date 2021/4/23 19:44
 * @desc 灰度发布服务配置
 */
@Component
@ConfigurationProperties(prefix = "server.gray")
@Setter
public class ServerGrayProperty {

    private Integer maxTryTimes;

    private Map<String, Config> config;

    public Config getConfig(String name) {
        if (MapUtils.isEmpty(config)) {
            return null;
        }
        return config.get(name);
    }

    @Data
    public static class Config {
        private String version;
        private Double rate;
    }

    public Integer getMaxTryTimes() {
        return maxTryTimes == null || maxTryTimes <= 0 ? 10 : maxTryTimes;
    }
}

实现灰度发布的关键Configuration:

package com.study.gateway.config;

import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.common.utils.MapUtils;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ClientConfigEnabledRoundRobinRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Collectors;

/**
 * @author zhaochao
 * @date 2021/4/24 10:26
 * @desc 自定义负载均衡策略 服务灰度发布配置
 */
@Slf4j
@Scope("prototype")
@Configuration
public class ServerGrayRule extends ClientConfigEnabledRoundRobinRule {

    private static final LongAdder GRAY_TIMES = new LongAdder();

    @Autowired
    private ServerGrayProperty serverGrayProperty;

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

    @Override
    public Server choose(Object o) {
        ILoadBalancer lb = this.getLoadBalancer();
        if (!(lb instanceof BaseLoadBalancer)) {
            return super.choose(o);
        }
        Server server = null;
        int count = 0;
        int max = serverGrayProperty.getMaxTryTimes();
        while (server == null && count++ < max) {
            if (Thread.interrupted()) {
                return null;
            }

            int allSize = ((BaseLoadBalancer) lb).getServerCount(false);

            if (allSize == 0) {
                return null;
            }

            // 灰度获取服务
            server = getServer((BaseLoadBalancer) lb, o);

            if (server == null) {
                Thread.yield();
                continue;
            }
            if (server.isAlive() && server.isReadyToServe()) {
                return server;
            }
            server = null;
            Thread.yield();
        }
        if (count >= max) {
            log.info("No available alive servers after " + max + " tries from load balancer: "
                    + lb);
        }
        return server;
    }

    private Server getServer(BaseLoadBalancer lb, Object o) {
        String name = lb.getName();
        ServerGrayProperty.Config config = serverGrayProperty.getConfig(name);
        if (Objects.isNull(config)) {
            return super.choose(o);
        }
        // 获取指定的灰度版本
        String targetVersion = config.getVersion();
        if (StringUtils.isBlank(targetVersion)) {
            return super.choose(o);
        }
        // 判断是否需要灰度
        boolean needGray = isNeedGray(config.getRate());
        // 不需要灰度
        if (!needGray) {
            return super.choose(o);
        }
        // 需要灰度
        List<Server> servers = lb.getAllServers();
        List<Server> grayServers = servers.stream().filter(s -> {
            if (!(s instanceof NacosServer)) {
                return false;
            }
            Instance instance = ((NacosServer) s).getInstance();
            if (instance == null) {
                return false;
            }
            Map<String, String> metadata = instance.getMetadata();
            if (MapUtils.isEmpty(metadata)) {
                return false;
            }
            String version = metadata.get("version");
            // 过滤指定的灰度版本
            if (!Objects.equals(targetVersion, version)) {
                return false;
            }
            return true;
        }).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(grayServers)) {
            log.error("No gray server to return");
            return null;
        }
        // 轮询获取灰度服务索引
        int index = getIndex(grayServers.size());
        return grayServers.get(index);
    }

    private int getIndex(int size) {
        if (size <= 1) {
            return 0;
        }
        GRAY_TIMES.increment();
        return GRAY_TIMES.intValue() % size;
    }

    /**
     * 判断当前是否需要返回灰度服务
     *
     * @param rate
     * @return
     */
    private boolean isNeedGray(Double rate) {
        // 未配置通过率或小于等于0,不需要灰度 不处理
        if (Objects.isNull(rate) || rate <= 0) {
            return false;
        }
        // 完全灰度
        if (Objects.equals(rate, 1.0)) {
            return true;
        }
        return Math.random() <= rate;
    }

}

测试服务producer的配置:

# 服务的元数据
spring.cloud.nacos.discovery.metadata.version=2021030V1

在nacos中修改gateway配置中的 version 和 rate 结合 producer服务的元数据,可以动态的实现灰度发布

IP

端口

临时实例

权重

健康状态

元数据

操作

192.168.0.106

8102

true

1

true

preserved.register.source=SPRING_CLOUD

version=20210423

编辑 下线

192.168.0.106

8101

true

1

true

preserved.register.source=SPRING_CLOUD

version=2021030V1

编辑 下线

192.168.0.106

8100

true

1

true

preserved.register.source=SPRING_CLOUD

version=2021030V1

编辑 下线

这样就实现了灰度发布。

可以继续进行拓展,结合运行机制及nacos数据存储结构等实现更为灵活的发布功能,不仅仅是灰度而已!

 

GitHub 加速计划 / na / nacos
29.83 K
12.75 K
下载
Nacos是由阿里巴巴开源的服务治理中间件,集成了动态服务发现、配置管理和服务元数据管理功能,广泛应用于微服务架构中,简化服务治理过程。
最近提交(Master分支:3 个月前 )
4334cd16 * Support custom client configuration timeout.(#12748) * Add UT.(#12748) 15 天前
b04d2266 19 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐