【灰度发布(三)】服务端灰度流量负载均衡规则实现
一. 服务间灰度路由的规则
核心原则参考如下图
1.灰度服务优先调度灰度服务
2.非灰度服务优先调度非灰度服务
3.下游目标灰度服务或非灰度服务不存在时,需要有兜底逻辑调用

二. Ribbon负载均衡规则
public class LoadBalancerRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
/**
* 灰度标签 标记灰度示例
*/
public static final String GRAY_TAG = "grayTag";
/**
* 灰度调用优先级 特殊服务需要优先调用灰度服务
*/
public static final String GRAY_PRIORITY = "grayPriorityTag";
@Override
public Server choose(Object key) {
List<Instance> allInstances = null;
try {
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
if (log.isDebugEnabled()) {
log.debug("开始执行负载均衡服务调用,当前服务名称:{},目标服务名称:{}", nacosDiscoveryProperties.getService(), loadBalancer.getName());
}
allInstances = loadBalancer.getReachableServers().stream()
.map(server -> ((NacosServer) server).getInstance())
.collect(Collectors.toList());
if (log.isDebugEnabled()) {
log.debug("目标服务实例信息列表:{}", JSON.toJSONString(allInstances));
}
//校验目标实例是否存在
if (CollectionUtils.isEmpty(allInstances)) {
log.warn("目标服务实例不存在:{}", loadBalancer.getName());
return null;
}
//获取当前实例元数据信息
Map<String, String> currentInstanceMetadataMap = nacosDiscoveryProperties.getMetadata();
if (log.isDebugEnabled()) {
log.debug("当前实例元数据信息:{}", currentInstanceMetadataMap);
}
boolean grayPriority = false;
String grayPriorityTag = currentInstanceMetadataMap.getOrDefault(GRAY_PRIORITY, GrayPriorityeEnum.DEFAULT.getCode());
String currentInstanceGrayTag = currentInstanceMetadataMap.getOrDefault(GRAY_TAG, "");
//扩展用于处理特殊实例, 校验是否是特殊实例,特殊实例优先执行灰度实例,
if (GrayPriorityeEnum.HIGH.getCode().equals(grayPriorityTag)) {
grayPriority = true;
}
//分类灰度实例和非灰度实例
List<Instance> grayInstances = new ArrayList<>();
List<Instance> normalInstances = new ArrayList<>();
for (Instance instance : allInstances) {
Map<String, String> targetMetadataMap = instance.getMetadata();
String targetInstanceGrayTag = targetMetadataMap.get(GRAY_TAG);
if (currentInstanceGrayTag.trim().equalsIgnoreCase(targetInstanceGrayTag.trim())) {
grayInstances.add(instance);
} else {
normalInstances.add(instance);
}
}
//灰度调用逻辑判断
Instance toBeChooseInstance = null;
if (grayPriority) {
if (!CollectionUtils.isEmpty(grayInstances)) {
toBeChooseInstance = ExtendBalancer.getHostByRandomWeight2(grayInstances);
} else {
toBeChooseInstance = ExtendBalancer.getHostByRandomWeight2(normalInstances);
}
} else {
List<Instance> waitChooseGrayInstances = allInstances.stream()
.filter(instance -> StringUtils.isNotBlank(instance.getMetadata().get(GRAY_TAG))
&& currentInstanceGrayTag.equals(instance.getMetadata().get(GRAY_TAG)))
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(waitChooseGrayInstances)) {
toBeChooseInstance = ExtendBalancer.getHostByRandomWeight2(waitChooseGrayInstances);
if (log.isDebugEnabled()) {
log.debug("执行灰度实例调用,灰度实例信息:{}", JSON.toJSONString(toBeChooseInstance));
}
} else {
toBeChooseInstance = ExtendBalancer.getHostByRandomWeight2(normalInstances);
}
}
if (ObjectUtils.isEmpty(toBeChooseInstance)) {
log.warn("灰度请求服务实例不存在,兜底从所有实例中默认选择一个实例");
toBeChooseInstance = ExtendBalancer.getHostByRandomWeight2(allInstances);
}
return new NacosServer(toBeChooseInstance);
} catch (Exception e) {
log.warn("NacosRule error:{}", e.getMessage(), e);
if (!CollectionUtils.isEmpty(allInstances)) {
return new NacosServer(ExtendBalancer.getHostByRandomWeight2(allInstances));
}
return null;
}
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
三. 关于 grayPriorityTag 属性的解释
由于部分服务中存在如下调用链,因此可以通过grayPriorityTag 当前服务优先进行目标灰度服务的调用

因此需要实现灰度调用优先级标签(grayPriorityTag) 自动刷新,由于Nacos discovery service 和 config service 逻辑上互相隔离,需要重写Nacos 配置变更监听器,实现服务实例 metadata信息变更同步到 Nacos 注册中心
public abstract class YamlListener extends AbstractListener
监听器实现
@Slf4j
@Component
@Conditional(value = GrayEnvCondition.class)
public class NacosGayMetadataComponent {
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
private Map<String, Object> localMap = new ConcurrentHashMap<>();
public static final String GRAY_PRIORITY = "grayPriorityTag";
@PostConstruct
public void init() {
try {
//服务启动 初始化版本号到本地缓存
Map<String, String> registryMetadataMap = nacosDiscoveryProperties.getMetadata();
localMap.put(GRAY_PRIORITY, registryMetadataMap.getOrDefault(GRAY_PRIORITY, ""));
//注册Nacos配置变化监听器
String service = nacosDiscoveryProperties.getService();
ConfigService configService = nacosConfigProperties.configServiceInstance();
String group = nacosConfigProperties.getGroup();
configService.addListener(service + "-" + nacosConfigProperties.getEnvironment().getActiveProfiles()[0] + "." + nacosConfigProperties.getFileExtension(), group, new YamlListener() {
@Override
public void innerReceive(String serverGrayTag) {
Object localGrayTag = localMap.getOrDefault(GRAY_PRIORITY, "");
if (log.isDebugEnabled()) {
log.debug("本地缓存灰度优先级标签 localGrayTag:{},配置中心实时灰度优先级标签 serverGrayTag:{}", localGrayTag, serverGrayTag);
}
if (StringUtils.isEmpty(localGrayTag) && StringUtils.isEmpty(serverGrayTag)) {
if (log.isDebugEnabled()) {
log.debug("本地缓存灰度优先级标签 与 配置中心实时灰度优先级标签,无需更新实例");
}
return;
}
if (localGrayTag.equals(serverGrayTag)) {
log.debug("本地缓存灰度优先级标签 与 配置中心实时灰度优先级标签 一致,无需更新");
return;
}
localMap.put(GRAY_PRIORITY, serverGrayTag);
log.info("开始更新服务实例灰度优先级标签元数据信息:{}",serverGrayTag);
String ip = nacosDiscoveryProperties.getIp();
Integer port = nacosDiscoveryProperties.getPort();
String service = nacosDiscoveryProperties.getService();
Instance instance = null;
try {
instance = getInstanceFromRegistry(nacosDiscoveryProperties, service, ip, port);
if (instance == null) {
log.warn("当前实例与注册中心服务列表中的实例均不匹配");
return;
}
if (StringUtils.isEmpty(serverGrayTag)) {
instance.getMetadata().remove(GRAY_PRIORITY);
} else {
instance.getMetadata().put(GRAY_PRIORITY, serverGrayTag);
}
nacosDiscoveryProperties.namingMaintainServiceInstance().updateInstance(service, instance);
} catch (Exception e) {
log.error("更新服务实例元数据信息失败:{}", e);
}
}
});
} catch (Exception e) {
log.error("初始化服务实例更新任务失败:{}",e.getMessage(), e);
}
}
/**
* 从注册中心获取实例信息
*
* @param nacosDiscoveryProperties {@link NacosDiscoveryProperties}
* @param service 当前实例服务名
* @param ip 当前实例IP
* @param port 当前实例端口
* @return 实例信息 {@link Instance}
* @throws NacosException
*/
private Instance getInstanceFromRegistry(NacosDiscoveryProperties nacosDiscoveryProperties, String service, String ip, Integer port) throws NacosException {
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
List<Instance> allInstances = namingService.selectInstances(service, true);
if (CollectionUtils.isEmpty(allInstances)) {
log.warn("注册中心服务列表实例为空:{}", service);
return null;
}
return allInstances.stream().filter(instance -> ip.equals(instance.getIp()) && port.equals(instance.getPort()))
.findFirst().orElse(null);
}
}
public class GrayEnvCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = context.getEnvironment();
if (environment instanceof StandardEnvironment) {
MutablePropertySources mutablePropertySources = ((StandardEnvironment) environment).getPropertySources();
return matchProp(mutablePropertySources);
}
return false;
}
private boolean matchProp(MutablePropertySources mutablePropertySources) {
Iterator<PropertySource<?>> iterator = mutablePropertySources.iterator();
for (Iterator<PropertySource<?>> it = iterator; it.hasNext(); ) {
Object property = it.next().getProperty("gray.config.auto-refresh");
if (!ObjectUtils.isEmpty(property)) {
return true;
}
}
return false;
}
}

浙公网安备 33010602011771号