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.


爱调味品的大哥