OpenFeign 整合 Nacos负载均衡原理

OpenFeign是实现微服务间调用的工具,功能包括编解码、构造http请求等。同时OpenFeign又集成了ribbon功能实现客户端负载均衡能力,ribbon默认的客户端负载均衡能力是与eurake集成,Nacos通过重写ribbon的ServerList功能实现ribbon与Nacos集成,这个调用过程就整个SpringCloudAlibaba的调用实现原理。

1.OpenFeign实现原理

OpenFeign入口在@EnableFeignClients注解,同时它又导入了FeignClientsRegistrar类

 OpenFeign 整合 Nacos负载均衡原理_第1张图片

 FeignClientsRegistrar类实现了ImportBeanDefinitionRegistrar类,这个类是Spring的一个扩展点,可以注册BeanDefinition,对外暴露了一个接口

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   registerDefaultConfiguration(metadata, registry);
   registerFeignClients(metadata, registry);
}

再进入registerFeignClients(metadata, registry)方法

public void registerFeignClients(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   ClassPathScanningCandidateComponentProvider scanner = getScanner();
   scanner.setResourceLoader(this.resourceLoader);

   Set basePackages;

   Map attrs = metadata
         .getAnnotationAttributes(EnableFeignClients.class.getName());
   AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
         FeignClient.class);
   final Class[] clients = attrs == null ? null
         : (Class[]) attrs.get("clients");
   if (clients == null || clients.length == 0) {
      scanner.addIncludeFilter(annotationTypeFilter);
      basePackages = getBasePackages(metadata);
   }
   else {
      final Set clientClasses = new HashSet<>();
      basePackages = new HashSet<>();
      for (Class clazz : clients) {
         basePackages.add(ClassUtils.getPackageName(clazz));
         clientClasses.add(clazz.getCanonicalName());
      }
      AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
         @Override
         protected boolean match(ClassMetadata metadata) {
            String cleaned = metadata.getClassName().replaceAll("\\$", ".");
            return clientClasses.contains(cleaned);
         }
      };
      scanner.addIncludeFilter(
            new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
   }

   for (String basePackage : basePackages) {
      Set candidateComponents = scanner
            .findCandidateComponents(basePackage);
      for (BeanDefinition candidateComponent : candidateComponents) {
         if (candidateComponent instanceof AnnotatedBeanDefinition) {
            // verify annotated class is an interface
            AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
            AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
            Assert.isTrue(annotationMetadata.isInterface(),
                  "@FeignClient can only be specified on an interface");

            Map attributes = annotationMetadata
                  .getAnnotationAttributes(
                        FeignClient.class.getCanonicalName());

            String name = getClientName(attributes);
            registerClientConfiguration(registry, name,
                  attributes.get("configuration"));

            registerFeignClient(registry, annotationMetadata, attributes);
         }
      }
   }
}

又进入registerFeignClient(registry, annotationMetadata, attributes)方法

private void registerFeignClient(BeanDefinitionRegistry registry,
      AnnotationMetadata annotationMetadata, Map attributes) {
   String className = annotationMetadata.getClassName();
   BeanDefinitionBuilder definition = BeanDefinitionBuilder
         .genericBeanDefinition(FeignClientFactoryBean.class);
   validate(attributes);
   definition.addPropertyValue("url", getUrl(attributes));
   definition.addPropertyValue("path", getPath(attributes));
   String name = getName(attributes);
   definition.addPropertyValue("name", name);
   definition.addPropertyValue("type", className);
   definition.addPropertyValue("decode404", attributes.get("decode404"));
   definition.addPropertyValue("fallback", attributes.get("fallback"));
   definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
   definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

   String alias = name + "FeignClient";
   AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

   boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null

   beanDefinition.setPrimary(primary);

   String qualifier = getQualifier(attributes);
   if (StringUtils.hasText(qualifier)) {
      alias = qualifier;
   }

   BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
         new String[] { alias });
   BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

这个方法是整个OpenFeign初始化客户端的核心,它注册了一个FeignClientFactoryBean类,这个类实现了FactoryBean接口,它是Spring中一个特殊的类,它可以返回“一类”对象,主要方法是getObject方法

@Override
public Object getObject() throws Exception {
   FeignContext context = applicationContext.getBean(FeignContext.class);
   Feign.Builder builder = feign(context);

   if (!StringUtils.hasText(this.url)) {
      String url;
      if (!this.name.startsWith("http")) {
         url = "http://" + this.name;
      }
      else {
         url = this.name;
      }
      url += cleanPath();
      return loadBalance(builder, context, new HardCodedTarget<>(this.type,
            this.name, url));
   }
   if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
      this.url = "http://" + this.url;
   }
   String url = this.url + cleanPath();
   Client client = getOptional(context, Client.class);
   if (client != null) {
      if (client instanceof LoadBalancerFeignClient) {
         // not load balancing because we have a url,
         // but ribbon is on the classpath, so unwrap
         client = ((LoadBalancerFeignClient)client).getDelegate();
      }
      builder.client(client);
   }
   Targeter targeter = get(context, Targeter.class);
   return targeter.target(this, builder, context, new HardCodedTarget<>(
         this.type, this.name, url));
}

也就是每次当我们注入Feign的接口时,Spring都会为我们创建一个接口的代理,而这个代理的生成都会走getObject方法。当我们没有为接口配置具体url时,使用服务名查找,而服务名查找就会走负载均衡方法,也就是loadBalance方法

这个方法中的client实现为LoadBalanceFeignClient类

OpenFeign 整合 Nacos负载均衡原理_第2张图片

 进入target方法

OpenFeign 整合 Nacos负载均衡原理_第3张图片

 最终生成代理

OpenFeign 整合 Nacos负载均衡原理_第4张图片

 OpenFeign 整合 Nacos负载均衡原理_第5张图片

 最终执行invoke方法

OpenFeign 整合 Nacos负载均衡原理_第6张图片

OpenFeign 整合 Nacos负载均衡原理_第7张图片

 OpenFeign 整合 Nacos负载均衡原理_第8张图片

 OpenFeign 整合 Nacos负载均衡原理_第9张图片

 使用LoadBalancerFeignClient的实现

OpenFeign 整合 Nacos负载均衡原理_第10张图片

 

OpenFeign 整合 Nacos负载均衡原理_第11张图片

 最终调用AbstractLoadBalancerAwareClient中的executeWithLoadBalancer方法,此时已经ribbon的方法了。

进入executeWithLoadBalancer方法

OpenFeign 整合 Nacos负载均衡原理_第12张图片

 

 进入submit方法,注意看selectServer()方法OpenFeign 整合 Nacos负载均衡原理_第13张图片

 

private Observable selectServer() {
    return Observable.create(new OnSubscribe() {
        @Override
        public void call(Subscriber next) {
            try {
                Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   
                next.onNext(server);
                next.onCompleted();
            } catch (Exception e) {
                next.onError(e);
            }
        }
    });
}

 这里就到了客户端负载均衡选择服务的地方了这里面有个细节是代码无法跟到的,在选择服务器,Nacos重写了ribbon的ServerList,这就是为什么ribbon可以读到Nacos注册中心的服务。我们看下Nacos自动装配文件

 再看下ribbon原始的自动装配文件。

 除了ServerList其他类几乎没动,这就完成了ribbon和Nacos的集成,整个调用过程还有很多细节,并没有跟进,后续有机会继续跟进

源码中的一些具体实现类:

Feign.Builder builder = SentinelFeign$Builder

Client client = LoadBalancerFeignClient

Targeter targeter = HystrixTargeter

DynamicServerListLoadBalancer

ZoneAwareLoadBalancer

你可能感兴趣的:(SpringBoot启动过程,spring,boot)