写于:2019-06-29 22:52:37

参考资料:Spring Cloud 官网

相关版本:Spring Boot 2.1.5.RELEASE 、Spring Cloud Greenwich.SR

一、回顾

《Feign如何进行服务间请求调用》 中,提到了 Feign 如何进行服务调用的。

Feign 在封装了相关的请求参数 RequestTemplate 后,发起服务远程请求调用。 相关部分代码如下:

  1. final class SynchronousMethodHandler implements MethodHandler {
  2. private final Client client;
  3. Object executeAndDecode(RequestTemplate template) throws Throwable {
  4. // 拼装完整的 request 请求
  5. Request request = targetRequest(template);
  6. // 发起请求,并获取返回结果 Response
  7. response = client.execute(request, options);
  8. // 解析 response 请求,获取最后的结果
  9. Object result = decode(response);
  10. // 响应返回结果
  11. return result;
  12. }
  13. }

从源码中可以很直观的看到真正发起服务请求调用的是 feign.Client#execute

feign.Client#execute 为切入点,进行分析。

二、Feign Ribbon Support

查看 feign.Clien 源码,feign.Client 是一个接口,它有两个实现类
02.png

  • feign.Client.Default
  • org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient

其中 LoadBalancerFeignClient 为 Ribbon 负载均衡客户端实现类。

2.1、LoadBalancerFeignClient 的注册

Feign 是如何注册 LoadBalancerFeignClient 作为其客户端调用实现的。

通过自动配置类 FeignRibbonClientAutoConfiguration 来看看

自动配置类 FeignRibbonClientAutoConfiguration 代码

  1. @ConditionalOnClass({ ILoadBalancer.class, Feign.class })
  2. @Configuration
  3. @AutoConfigureBefore(FeignAutoConfiguration.class)
  4. @EnableConfigurationProperties({ FeignHttpClientProperties.class })
  5. @Import({ HttpClientFeignLoadBalancedConfiguration.class,
  6. OkHttpFeignLoadBalancedConfiguration.class,
  7. DefaultFeignLoadBalancedConfiguration.class })
  8. public class FeignRibbonClientAutoConfiguration {
  9. @Bean
  10. @Primary
  11. @ConditionalOnMissingBean
  12. @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  13. public CachingSpringLoadBalancerFactory cachingLBClientFactory(
  14. SpringClientFactory factory) {
  15. return new CachingSpringLoadBalancerFactory(factory);
  16. }
  17. @Bean
  18. @ConditionalOnMissingBean
  19. public Request.Options feignRequestOptions() {
  20. return LoadBalancerFeignClient.DEFAULT_OPTIONS;
  21. }
  22. }

关注点:

  • 1、 @ConditionalOnClass({ ILoadBalancer.class, Feign.class })
    其中 ILoadBalancer.class 是 Ribbon 依赖中的类。换句话说,该配置需要 Ribbon 和 Feign 的相关依赖都引入才会生效。
  • 2、 @AutoConfigureBefore(FeignAutoConfiguration.class)
    如果该自动配置类生效,则在 FeignAutoConfiguration 之前进行配置,因为 FeignAutoConfiguration 关联到了 Feign Client 的代理对象的实例化,而其中真实发起请求调用的 Client 对象实例在 Feign Client 调用 Fiegn.Build 进行实例化时 Client 实现对象 需要提前实例化。 换句话说,如果 Client 对应Ribbon 的实现类 LoadBalancerFeignClient 存在,就是用 Ribbon 的负载均衡客户端进行请求调用处理,反之,使用默认的 feign.Client.Default。
  • 3、@Import({DefaultFeignLoadBalancedConfiguration.class})
    DefaultFeignLoadBalancedConfiguration 是一个 Bean 配置类,对 LoadBalancerFeignClient 进行了实例化配置。

三、LoadBalancerFeignClient 客户端负载均衡

前提:项目中引入了 Ribbon 相关依赖,Feign Client 执行调用的 Client#execute 方法中而Client 实现类为 LoadBalancerFeignClient。

3.1、分析 LoadBalancerFeignClient#execute

请求参数分析

调用 LoadBalancerFeignClient#execute 需要传递两个参数:RequestRequest.Options

其中参数 Request 数据结构如下
01.png

Request.Options 为 Ribbon 请求配置信息

execute 方法分析

  1. public class LoadBalancerFeignClient implements Client {
  2. @Override
  3. public Response execute(Request request, Request.Options options) throws IOException {
  4. // 根据条件参数 asUri = http://provider/user/save?userId=1
  5. URI asUri = URI.create(request.url());
  6. // 根据条件参数 clientName = provider
  7. String clientName = asUri.getHost();
  8. // 根据条件参数 uriWithoutHout = http:///user/save?userId=1
  9. URI uriWithoutHost = cleanUrl(request.url(), clientName);
  10. // 根据 uriWithoutHost 构造 RibbonReuqest
  11. FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost);
  12. // 获取请求相关配置信息
  13. IClientConfig requestConfig = getClientConfig(options, clientName);
  14. // 执行操作
  15. return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
  16. }
  17. }

代码 LoadBalancerFeignClient#execute 大体流程如下

  • step1、封装 Ribbon 请求信息
  • step2、获取 Ribbon 请求相关配置信息
    LoadBalancerFeignClient#getClientConfig
  • step3、根据封装的 RibbonRequest (ribbon请求) 和 requestConfig (ribbon请求配置属性) 发起请求。

下面具体看看 步骤二 和步骤三

步骤二:获取 Ribbon 请求配置参数信息

聚焦LoadBalancerFeignClient#getClientConfig

  1. public class LoadBalancerFeignClient implements Client {
  2. IClientConfig getClientConfig(Request.Options options, String clientName) {
  3. IClientConfig requestConfig;
  4. if (options == DEFAULT_OPTIONS) {
  5. requestConfig = this.clientFactory.getClientConfig(clientName);
  6. }
  7. else {
  8. requestConfig = new FeignOptionsClientConfig(options);
  9. }
  10. return requestConfig;
  11. }
  12. }

根据 Request.Options 选择 ribbon 配置的方式

  • 选择一、 通过调用 SpringClientFactory#getClientConfig 获取。
    SpringClientFactory 在 Ribbon 的自动配置类 RibbonAutoConfiguration#springClientFactory 中进行实例化,在 FeignRibbonClientAutoConfiguration 自动配置类 进行 LoadBalancerFeignClient Bean 配置实例化作为构造参数传入。
  • 选择二、自定义实例化 FeignOptionsClientConfig 配置类。
    FeignOptionsClientConfig 继承了 DefaultClientConfigImpl 。 DefaultClientConfigImpl 实现了 IClientConfig。与方式一获取到的 Ribbon 配置实现自同一个接口 IClientConfig。

步骤三:根据 RibbonRequest 和 IClientConfig 发起请求。

LoadBalancerFeignClient#execute return 执行的代码如下

  1. lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();

该代码中,包括两个执行逻辑

  • LoadBalancerFeignClient#lbClient
  • FeignLoadBalancer#executeWithLoadBalancer

我们分别来看看这两个执行逻辑。

聚焦LoadBalancerFeignClient#lbClient

  1. public class LoadBalancerFeignClient implements Client {
  2. private CachingSpringLoadBalancerFactory lbClientFactory;
  3. private FeignLoadBalancer lbClient(String clientName) {
  4. return this.lbClientFactory.create(clientName);
  5. }
  6. }

LoadBalancerFeignClient#lbClient 逻辑:通过 clientName 获取 FeignLoadBalancer 对象。

而 FeignLoadBalancer 对象的获取是从 CachingSpringLoadBalancerFactory#create 中获取的。

  1. public class CachingSpringLoadBalancerFactory {
  2. protected final SpringClientFactory factory;
  3. private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
  4. public FeignLoadBalancer create(String clientName) {
  5. FeignLoadBalancer client = this.cache.get(clientName);
  6. if (client != null) {
  7. return client;
  8. }
  9. IClientConfig config = this.factory.getClientConfig(clientName);
  10. ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
  11. ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
  12. ServerIntrospector.class);
  13. client = this.loadBalancedRetryFactory != null
  14. ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
  15. this.loadBalancedRetryFactory)
  16. : new FeignLoadBalancer(lb, config, serverIntrospector);
  17. this.cache.put(clientName, client);
  18. return client;
  19. }
  20. }

CachingSpringLoadBalancerFactory 是一个缓存工厂,缓存了 clientName 对应 FeignLoadBalancer 关系结构。

通过代码,我们可以知道 FeignLoadBalancer 是实现 Feign 客户端负载均衡调用的一个类, FeignLoadBalancer 对 Ribbon ILoadBalancer 进行了封装,将 RIbbon 的数据 如:ILoadBalancer, IClientConfig 直接以成员变量的形式存入 FeignLoadBalancer 中。

CachingSpringLoadBalancerFactory 在 FeignRibbonClientAutoConfiguration 自动配置类中进行实例化,然后被作为实例化 LoadBalancerFeignClient 的构造参数。 在使用 Eureka Server 来维护服务列表时 FeignLoadBalancer 中的 ILoadBalancer 对应的是 DynamicServerListLoadBalancer ,DynamicServerListLoadBalancer 中维护的服务列表信息动态从 Eureka Server 中更新。 友情链接:《客户端负载均衡:Ribbon》

在得到 FeignLoadBalancer 客户端负载均衡实例对象之后,就是开始调用 FeignLoadBalancer#executeWithLoadBalancer。

而 FeignLoadBalancer#executeWithLoadBalancer 方法是由其父类 AbstractLoadBalancerAwareClient 实现的。AbstractLoadBalancerAwareClient 属于 Ribbon 模块。

聚焦 AbstractLoadBalancerAwareClient#executeWithLoadBalancer

  1. public abstract class AbstractLoadBalancerAwareClient ......{
  2. public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
  3. LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
  4. return command.submit(
  5. (server) -> {
  6. ......
  7. return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
  8. ......
  9. }
  10. );
  11. }
  12. }

该方法有两个主要逻辑:

  • LoadBalancerCommand#submit
    该方法实现客户端负载均衡,根据 IRule(官方默认实现: ZoneAvoidanceRule) 从 ILoadBalancer(Eureka服务实例管理,默认:DynamicServerListLoadBalancer) 中获取一个Server实例对象。
  • this.execute(requestForServer, requestConfig)
    根据 LoadBalancerCommand#submit 获取到的 Server ,调用 FeignLoadBalancer#execute 发起服务调用请求,并返回结果。

针对 ZoneAvoidanceRule 规则等不进行展开。

3.2、总结

1、LoadBalancerFeignClient#execute() 是整个 Feign Client 的执行入口

2、LoadBalancerFeignClient#execute() 方法主线逻辑:

  • 封装 RibbonReuqest 请求对象
  • 从容器中获取 Ribbon 请求配置参数信息 IConfigClient
  • 根据 RibbonRequest 和 IConfigClient 构造 FeignLoadBalancer 。

3、FeignLoadBalancer 根据 Ribbon 的 IRule 规则从 Iloadbalancer 获取一个服务实例 server。

4、FeignLoadBalancer#execute 根据获取到的服务实例 Server 发起请求调用。然后返回。

四、总结

首先我们再来回顾一下Feign Client 发起请求的代码:

  1. final class SynchronousMethodHandler implements MethodHandler {
  2. private final Client client;
  3. Object executeAndDecode(RequestTemplate template) throws Throwable {
  4. // 拼装完整的 request 请求
  5. Request request = targetRequest(template);
  6. // 发起请求,并获取返回结果 Response
  7. response = client.execute(request, options);
  8. // 解析 response 请求,获取最后的结果
  9. Object result = decode(response);
  10. // 响应返回结果
  11. return result;
  12. }
  13. }

Feign Client 发起请求是由 Client 来进行处理。Client 默认实现类 Default 不支持客户端负载均衡功能。

当项目应用中引入了 Ribbon 相关依赖,触发自动配置类 FeignRibbonClientAutoConfiguration。

Feign Client 发起请求的 Client 实例对象由 Client.Default 变更为 LoadBalancerFeignClient。

LoadBalancerFeignClient#execute 发起请求调用,会生成 FeignLoadBalancer 来实现客户端负载均衡。

FeignLoadBalancer 其实就是 Feign 对 Ribbon 的一层封装,实际调用的就是 Ribbon 的负载均衡逻辑,包括 IRule 等 进行服务实例的获取。