参考资料:Spring Cloud 官网
相关版本:Spring Boot 2.1.5.RELEASE 、Spring Cloud Greenwich.SR
在 Spring Cloud Feign【源码篇】Feign 如何进行服务间请求调用 中,提到了 Feign 如何进行服务调用的。
Feign 在封装了相关的请求参数 RequestTemplate 后,发起服务远程请求调用。 代码如下:
final class SynchronousMethodHandler implements MethodHandler {
private final Client client;
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 拼装完整的 request 请求
Request request = targetRequest(template);
// 发起请求,并获取返回结果 Response
response = client.execute(request, options);
// 解析 response 请求,获取最后的结果
Object result = decode(response);
// 响应返回结果
return result;
}
}
从源码中可以很直观的看到真正发起服务请求调用的是 feign.Client#execute
以 feign.Client#execute 为切入点,进行分析。
查看 feign.Clien 源码,feign.Client 是一个接口,它有两个实现类
feign.Client.Default
org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
其中 LoadBalancerFeignClient 为 Ribbon 负载均衡客户端实现类。
Feign 是如何注册 LoadBalancerFeignClient 作为其客户端调用实现的。首先想到的就是自动配置类 FeignRibbonClientAutoConfiguration。
聚焦 FeignRibbonClientAutoConfiguration
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory cachingLBClientFactory(
SpringClientFactory factory) {
return new CachingSpringLoadBalancerFactory(factory);
}
@Bean
@ConditionalOnMissingBean
public Request.Options feignRequestOptions() {
return LoadBalancerFeignClient.DEFAULT_OPTIONS;
}
}
关注点:
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 进行了实例化配置。
1、 Feign Client 在执行调用最终执行的是 Client#execute。
2、 Client 是一个接口,其实现类 LoadBalancerFeignClient 和 Default。在 Feign 代理对象实例化作为属性存入 Feign Client 代理对象中。
3、 当应用依赖中引入 Ribbon 相关依赖时,在 Feign 代理对象实例化前,会先生成 Client Ribbon 实现类 LoadBalancerFeignClient 的实例对象。反之,使用默认的 feign.Default 。
前提:Feign Client 最终执行调用的是 Client#execute 方法,而此时 Client 实现类是 LoadBalancerFeignClient。
调用 LoadBalancerFeignClient#execute 需要传递两个参数:Request 和 Request.Options
其中参数 Request 数据结构如下
而 Request.Options 是进行 Ribbon 配置获取的选择条件
聚焦 LoadBalancerFeignClient#execute
public class LoadBalancerFeignClient implements Client {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
// 根据条件参数 asUri = http://provider/user/save?userId=1
URI asUri = URI.create(request.url());
// 根据条件参数 clientName = provider
String clientName = asUri.getHost();
// 根据条件参数 uriWithoutHout = http:///user/save?userId=1
URI uriWithoutHost = cleanUrl(request.url(), clientName);
// 根据 uriWithoutHost 构造 RibbonReuqest
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost);
// 获取请求相关配置信息
IClientConfig requestConfig = getClientConfig(options, clientName);
// 执行操作
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
}
代码 LoadBalancerFeignClient#execute 大体流程如下
step1、封装 Ribbon 请求信息
step2、获取 Ribbon 请求相关配置信息
LoadBalancerFeignClient#getClientConfig
step3、根据封装的 RibbonRequest (ribbon请求) 和 requestConfig (ribbon请求配置属性) 发起请求。
下面具体看看 步骤二 和步骤三
聚焦LoadBalancerFeignClient#getClientConfig
public class LoadBalancerFeignClient implements Client {
IClientConfig getClientConfig(Request.Options options, String clientName) {
IClientConfig requestConfig;
if (options == DEFAULT_OPTIONS) {
requestConfig = this.clientFactory.getClientConfig(clientName);
}
else {
requestConfig = new FeignOptionsClientConfig(options);
}
return requestConfig;
}
}
根据 Request.Options 选择 ribbon 配置的方式
选择一、 通过调用 SpringClientFactory#getClientConfig 获取。
SpringClientFactory 在 Ribbon 的自动配置类 RibbonAutoConfiguration#springClientFactory 中进行实例化,在 FeignRibbonClientAutoConfiguration 自动配置类 进行 LoadBalancerFeignClient Bean 配置实例化作为构造参数传入。
选择二、自定义实例化 FeignOptionsClientConfig 配置类。
FeignOptionsClientConfig 继承了 DefaultClientConfigImpl 。 DefaultClientConfigImpl 实现了 IClientConfig。与方式一获取到的 Ribbon 配置实现自同一个接口 IClientConfig。
LoadBalancerFeignClient#execute return 执行的代码如下
lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
该代码中,包括两个执行逻辑
我们分别来看看这两个执行逻辑。
聚焦LoadBalancerFeignClient#lbClient
public class LoadBalancerFeignClient implements Client {
private CachingSpringLoadBalancerFactory lbClientFactory;
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
}
LoadBalancerFeignClient#lbClient 逻辑:通过 clientName 获取 FeignLoadBalancer 对象。
而 FeignLoadBalancer 对象的获取是从 CachingSpringLoadBalancerFactory#create 中获取的。
public class CachingSpringLoadBalancerFactory {
protected final SpringClientFactory factory;
private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
public FeignLoadBalancer create(String clientName) {
FeignLoadBalancer client = this.cache.get(clientName);
if (client != null) {
return client;
}
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
ServerIntrospector.class);
client = this.loadBalancedRetryFactory != null
? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
this.loadBalancedRetryFactory)
: new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
}
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
public abstract class AbstractLoadBalancerAwareClient ......{
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
return command.submit(
(server) -> {
......
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
......
}
);
}
}
该方法有两个主要逻辑:
LoadBalancerCommand#submit
该方法实现客户端负载均衡,根据 IRule(官方默认实现: ZoneAvoidanceRule) 从 ILoadBalancer(Eureka服务实例管理,默认:DynamicServerListLoadBalancer) 中获取一个Server实例对象。
this.execute(requestForServer, requestConfig)
根据 LoadBalancerCommand#submit 获取到的 Server ,调用 FeignLoadBalancer#execute 发起服务调用请求,并返回结果。
针对 ZoneAvoidanceRule 规则等不进行展开。
1、LoadBalancerFeignClient#execute() 是整个 Feign Client 的执行入口
2、LoadBalancerFeignClient#execute() 方法主线逻辑:
首先我们再来回顾一下Feign Client 发起请求的代码:
final class SynchronousMethodHandler implements MethodHandler {
private final Client client;
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 拼装完整的 request 请求
Request request = targetRequest(template);
// 发起请求,并获取返回结果 Response
response = client.execute(request, options);
// 解析 response 请求,获取最后的结果
Object result = decode(response);
// 响应返回结果
return result;
}
}
Feign Client 发起请求是由 Client 来进行处理。Client 默认实现类 Default 不支持客户端负载均衡功能。
当项目应用中引入了 Ribbon 相关依赖,触发自动配置类 FeignRibbonClientAutoConfiguration。
Feign Client 发起请求的 Client 实例对象由 Client.Default 变更为 LoadBalancerFeignClient。
LoadBalancerFeignClient#execute 发起请求调用,会生成 FeignLoadBalancer 来实现客户端负载均衡。
FeignLoadBalancer 其实就是 Feign 对 Ribbon 的一层封装,实际调用的就是 Ribbon 的负载均衡逻辑,包括 IRule 等 进行服务实例的获取。