SpringCloudGateway实现灰度发布,结合nacos支持不同服务配置不同灰度版本及灰度权重(概率)
nacos
Nacos是由阿里巴巴开源的服务治理中间件,集成了动态服务发现、配置管理和服务元数据管理功能,广泛应用于微服务架构中,简化服务治理过程。
项目地址:https://gitcode.com/gh_mirrors/na/nacos
免费下载资源
·
网上查看现有的一些实现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 天前
更多推荐
已为社区贡献1条内容
所有评论(0)