OpenFeign学习(十):Spring Cloud OpenFeign 集成 Ribbon,Eureka 实现请求负载均衡流程解析
说明
通过之前的博文,我简单介绍了 Spring Cloud OpenFeign 的使用方式及加载配置原理,以及在脱离 Eureka 的情况下使用 Ribbon 以及 Spring Cloud Ribbon 的加载配置原理。通过这些内容我们简单了解到了 OpenFeign 和 Ribbon 的使用方式以及 Spring Cloud 是如何对这些组件进行集成加载配置的。
本篇博文,我将继续通过源码来简单介绍 Spring Cloud OpenFeign 在集成 Eureka,Ribbon 后如何实现自动请求负载。
正文
NamedContextFactory
先回忆下Spring Cloud OpenFeign 相关内容,通过 @FeignClient 和 @EnableFeignClients 注解配置 FeignClient,再通过 FeignContext 获取对应 clientName 的应用上下文。详看《OpenFeign学习(七):Spring Cloud OpenFeign的使用》和《OpenFeign学习(九):Spring Cloud OpenFeign的加载配置原理 II》。
在使用 Ribbon时,通过 @RabbionClient 注解配置 RibbonClient,再通过 SpringClientFactory 获取对应 clientName 的应用上下文。详看《Ribbon 学习(一):脱离 Eureka 使用 Ribbon》和《Ribbon 学习(二):Spring Cloud Ribbon 加载配置原理》。
通过源码我们可以发现,SpringClientFactory 和 FeignContext 都继承自 NamedContextFactory 抽象类。
FeignRibbonClientAutoConfiguration
针对该类,我在之前的博文《OpenFeign学习(九):Spring Cloud OpenFeign的加载配置原理 II》中,提到该类是 RibbonClient 的支持类,通过引入不同的配置类,根据配置创建不同的 feignClient。默认创建的为 LoadBalancerFeignClient 类型。在之前我们忽略了其作为配置类,在其内部定义的 Bean。
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
public CachingSpringLoadBalancerFactory cachingLBClientFactory(SpringClientFactory factory) {
return new CachingSpringLoadBalancerFactory(factory);
}
可以看到,在内部定义配置了 CachingSpringLoadBalancerFactory ,同时在创建生成时注入了 Ribbon 的 SpringClientFactory。从名称可以看出,该类用来创建缓存 feignClient 对应的负载均衡器。
对于 SpringClientFactory 类,我在《Ribbon 学习(二):Spring Cloud Ribbon 加载配置原理》一文中提到,该类是创建 Ribbon 客户端,负载均衡器,客户端配置实例的工厂,将 RibbonClientConfiguration 类作为所有 RibbonClient 的默认配置类,并且将所有 @RibbonClient 定义生成的 RibbonClientSpecification 集合作为参数传递到 NamedContextFactory,作为创建 Ribbon Client 上下文的依据。
RibbonEurekaAutoConfiguration
@Configuration
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter({RibbonAutoConfiguration.class})
@RibbonClients(
defaultConfiguration = {EurekaRibbonClientConfiguration.class}
)
public class RibbonEurekaAutoConfiguration {
public RibbonEurekaAutoConfiguration() {
}
}
该类是 Ribbon 对 Eureka 的自动配置类,通过源码可以看到,该类在 RibbonAutoConfiguration 之后配置,并且该类使用了 @RibbonClients 注解设置了 RibbonClient 的默认配置类 EurekaRibbonClientConfiguration。
EurekaRibbonClientConfiguration
@Configuration
public class EurekaRibbonClientConfiguration {
...
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
if (this.propertiesFactory.isSet(IPing.class, this.serviceId)) {
return (IPing)this.propertiesFactory.get(IPing.class, config, this.serviceId);
} else {
NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
ping.initWithNiwsConfig(config);
return ping;
}
}
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {
if (this.propertiesFactory.isSet(ServerList.class, this.serviceId)) {
return (ServerList)this.propertiesFactory.get(ServerList.class, config, this.serviceId);
} else {
DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(config, eurekaClientProvider);
DomainExtractingServerList serverList = new DomainExtractingServerList(discoveryServerList, config, this.approximateZoneFromHostname);
return serverList;
}
}
...
}
在该类中配置了Ribbon Client 的 IPing 和 ServerList,在之前博文中我介绍了 Spring 为 Ribbon Client 提供了默认配置类,IPing 对应为 DummyPing,ServerList 对应为 ConfigurationBasedServerList。在这里可以看到,通过 PropertiesFactory 判断是否为该 Client 配置 IPing,若没有则生成 NIWSDiscoveryPing 作为默认配置。ServerList 同理,使用 DiscoveryEnabledNIWSServerList 作为默认配置。
LoadBalancerFeignClient.execute
在配置完成后,进入 Feign 的请求流程,通过 SynchronousMethodHandler 代理到 LoadBalancerFeignClient 的 execute 方法。
public Response execute(Request request, Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = this.getClientConfig(options, clientName);
return ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
} catch (ClientException var8) {
IOException io = this.findIOException(var8);
if (io != null) {
throw io;
} else {
throw new RuntimeException(var8);
}
}
}
可以看到在该方法中,通过请求 url 获取对应的服务名,构建 RibbonRequest,IClientConfig 对象,最后通过 lbClient 方法创建对应 FeignLoadBalancer。
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
通过 CachingSpringLoadBalancerFactory 的 create 方法创建 FeignLoadBalancer。
public FeignLoadBalancer create(String clientName) {
FeignLoadBalancer client = (FeignLoadBalancer)this.cache.get(clientName);
if (client != null) {
return client;
} else {
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = (ServerIntrospector)this.factory.getInstance(clientName, ServerIntrospector.class);
FeignLoadBalancer client = this.loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return (FeignLoadBalancer)client;
}
}
在初次请求时会创建 FeignLoadBalancer 实例,第一次创建时通过 SpringClientFactory 来创建对应 的 AnnotationConfigApplicationContext,在这里会初始化之前通过 EurekaRibbonClientConfiguration 和 RibbonClientSpecification 配置类定义的 bean。创建初始化 IClientConfig, IPing,ServerList,ILoadBalancer 等对象。
在初始化 ZoneAwareLoadBalancer 对象时,会通过 DomainExtractingServerList 的 getUpdatedListOfServers 方法获取服务地址列表,DomainExtractingServerList 由 EurekaRibbonClientConfiguration 定义配置,代理了 DiscoveryEnabledNIWSServerList 类,最终调用 DiscoveryEnabledNIWSServerList 的 obtainServersViaDiscovery 方法从 Eureka Server 获取服务地址。
FeignLoadBalancer
在创建完成 FeignLoadBalancer 对象后,调用其父类 AbstractLoadBalancerAwareClient 的 executeWithLoadBalancer 方法。在该方法中调用 FeignLoadBalancer 的 execute 方法执行请求。
executeWithLoadBalancer
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
// 创建 Ribbon 的 Command 命令用来控制请求的执行流程
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
// 用 lambda 表达式声明实现 ServerOperation 接口
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
// 获取到 server 后重构 URI
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
// 调用 FeignLoadBalancer 的 execute 方法执行请求
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
通过源码可以看到,Ribbon 创建了 LoadBalancerCommand 对象,采用与 Hystrix 类似的命令模式来控制请求流程。
在 submit 方法中用 lambda 表达式声明了在获取到 server 后的处理流程,在该方法中调用了 FeignLoadBalancer 的 execute 方法。
selectServer
在 LoadBalancerCommand 中通过 selectServer 方法获取服务地址,本质是通过 ZoneAwareLoadBalancer 的 chooseServer 方法获取,根据 IRule 进行选择。
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
execute
public FeignLoadBalancer.RibbonResponse execute(FeignLoadBalancer.RibbonRequest request, IClientConfig configOverride) throws IOException {
Options options;
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Options(override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout));
} else {
options = new Options(this.connectTimeout, this.readTimeout);
}
Response response = request.client().execute(request.toRequest(), options);
return new FeignLoadBalancer.RibbonResponse(request.getUri(), response);
}
最后,通过 RibbonRequest 中封装的 client 进行请求。在这里使用的是 Client 的默认实现类 Default,底层使用的是 HttpURLConnection。
总结
在 Spring Cloud OpenFeign 集成 Ribbon,Eureka 后,会自动进行请求负载。在加载配置时,FeignRibbonClientAutoConfiguration 配置类定义配置了 feign.Client 接口的实现。默认为 LoadBalancerFeignClient。
在 RibbonAutoConfiguration 加载后,RibbonEurekaAutoConfiguration 配置类被加载,该类使用了 @RibbonClients 注解,并设置了默认配置类 EurekaRibbonClientConfiguration,定义配置了 IPing,ServerList 等 Bean。
配置完成后,通过 FeignClient 进行服务器请求,代理到 LoadBalancerFeignClient 进行负载,这里会创建 RibbonRequest 对象,并生成实际的负载客户端 FeignLoadBalancer。
通过 Ribbon 的 SpringClientFactory 来创建对应 clientName 的应用上下文 AnnotationConfigApplicationContext,由于会先加载通过 @RibbonClient 或 @RibbonClients 设置的配置类,所以 EurekaRibbonClientConfiguration 配置的 IPing,ServerList 会优先被加载配置,Ribbon 对应 Eureka 加载类分别为 NIWSDiscoveryPing 和 DiscoveryEnabledNIWSServerList。
之后调用 FeignLoadBalancer 的父类 AbstractLoadBalancerAwareClient 的 executeWithLoadBalancer 方法继续执行请求。在该方法中创建 LoadBalancerCommand 对象进行请求流程控制,在方法执行过程中,通过 LoadBalancerCommand 的 selectServer 方法使用 Ribbon 的 ZoneAwareLoadBalancer 进行服务实例的选择。
最后,获取到服务实例后,重新构建 URI,再通过 LoadBalancerFeignClient 的代理类 Default 进行请求,实际是通过 HttpURLConnection 进行请求。
还没有评论,来说两句吧...