springcloud-扩展Ribbon支持基于元数据的版本管理
上一篇已经实现了:
-
优先调用同集群下的实例
-
实现基于权重配置的负载均衡
但实际项目,我们可能还会有这样的需求:
-
一个微服务在线上可能多版本共存,例如:
-
服务提供者有两个版本:v1、v2
-
服务消费者也有两个版本:v1、v2
-
v1/v2是不兼容的。服务消费者v1只能调用服务提供者v1;消费者v2只能调用提供者v2。如何实现呢?
下面围绕该场景,实现微服务之间的版本控制。
元数据
元数据就是一堆的描述信息,以map存储。举个例子:
spring: cloud: nacos: metadata: # 自己这个实例的版本 version: v1 # 允许调用的提供者版本 target-version: v1
- 需求分析,我们需要实现的有两点:
优先选择同集群下,符合metadata的实例
如果同集群加没有符合metadata的实例,就选择所有集群下,符合metadata的实例
写代码
@Slf4j public class NacosMetadataRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } @Override public Server choose(Object key) { //--- 负载均衡规则:优先选择同集群下,符合metadata的实例,如果没有,就选择所有集群下,符合metadata的实例---- //获取服务集群 String clusterName=discoveryProperties.getClusterName(); //获取当前服务的原元数据 Map<String, String> metadata = discoveryProperties.getMetadata(); //当前服务可以调用的服务版本 String target_version=metadata.get("target-version"); BaseLoadBalancer loadBalancer= (BaseLoadBalancer) this.getLoadBalancer(); //想要请求的微服务名称 String name =loadBalancer.getName(); NamingService namingService=discoveryProperties.namingServiceInstance(); try { //所有服务实例 List<Instance> instances = namingService.selectInstances(name, true); List<Instance> metadataMatchInstances = instances; //配置了元数据 if(StringUtils.isNotEmpty(target_version)){ metadataMatchInstances = instances.stream() .filter(instance -> Objects.equals(target_version, instance.getMetadata().get("version"))) .collect(Collectors.toList()); if(CollectionUtils.isEmpty(metadataMatchInstances)){ log.warn("未找到元数据匹配的目标实例!请检查配置。targetVersion = {}, instance = {}", target_version, instances); return null; } } List<Instance> clusterMetadataMatchInstances = metadataMatchInstances; //配置了服务集群 if(StringUtils.isNotEmpty(clusterName)){ clusterMetadataMatchInstances= metadataMatchInstances.stream(). filter(instance -> Objects.equals(clusterName,instance.getClusterName())). collect(Collectors.toList()); if(CollectionUtils.isEmpty(clusterMetadataMatchInstances)){ log.warn("发生跨集群调用。clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, target_version, clusterMetadataMatchInstances); clusterMetadataMatchInstances=metadataMatchInstances; } } Instance instance=LoadBalancer.getHost(clusterMetadataMatchInstances); log.debug("选择的instance:port={},instanceId={}",instance.getPort(),instance.getInstanceId()); return new NacosServer(instance); } catch (NacosException e) { e.printStackTrace(); } return null; } /** * 调用权重负载均衡算法 原Balancer方法无法调用 */ static class LoadBalancer extends Balancer { public static Instance getHost(List<Instance> hosts) { return getHostByRandomWeight( hosts); } } }
Q.E.D.