OpenFeign学习(十):Spring Cloud OpenFeign 集成 Ribbon,Eureka 实现请求负载均衡流程解析

绝地灬酷狼 2022-12-12 04:37 249阅读 0赞

说明

通过之前的博文,我简单介绍了 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。

  1. @Bean
  2. @Primary
  3. @ConditionalOnMissingBean
  4. @ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
  5. public CachingSpringLoadBalancerFactory cachingLBClientFactory(SpringClientFactory factory) {
  6. return new CachingSpringLoadBalancerFactory(factory);
  7. }

可以看到,在内部定义配置了 CachingSpringLoadBalancerFactory ,同时在创建生成时注入了 Ribbon 的 SpringClientFactory。从名称可以看出,该类用来创建缓存 feignClient 对应的负载均衡器。

对于 SpringClientFactory 类,我在《Ribbon 学习(二):Spring Cloud Ribbon 加载配置原理》一文中提到,该类是创建 Ribbon 客户端,负载均衡器,客户端配置实例的工厂,将 RibbonClientConfiguration 类作为所有 RibbonClient 的默认配置类,并且将所有 @RibbonClient 定义生成的 RibbonClientSpecification 集合作为参数传递到 NamedContextFactory,作为创建 Ribbon Client 上下文的依据。

RibbonEurekaAutoConfiguration

  1. @Configuration
  2. @EnableConfigurationProperties
  3. @ConditionalOnRibbonAndEurekaEnabled
  4. @AutoConfigureAfter({RibbonAutoConfiguration.class})
  5. @RibbonClients(
  6. defaultConfiguration = {EurekaRibbonClientConfiguration.class}
  7. )
  8. public class RibbonEurekaAutoConfiguration {
  9. public RibbonEurekaAutoConfiguration() {
  10. }
  11. }

该类是 Ribbon 对 Eureka 的自动配置类,通过源码可以看到,该类在 RibbonAutoConfiguration 之后配置,并且该类使用了 @RibbonClients 注解设置了 RibbonClient 的默认配置类 EurekaRibbonClientConfiguration。

EurekaRibbonClientConfiguration

  1. @Configuration
  2. public class EurekaRibbonClientConfiguration {
  3. ...
  4. @Bean
  5. @ConditionalOnMissingBean
  6. public IPing ribbonPing(IClientConfig config) {
  7. if (this.propertiesFactory.isSet(IPing.class, this.serviceId)) {
  8. return (IPing)this.propertiesFactory.get(IPing.class, config, this.serviceId);
  9. } else {
  10. NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
  11. ping.initWithNiwsConfig(config);
  12. return ping;
  13. }
  14. }
  15. @Bean
  16. @ConditionalOnMissingBean
  17. public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {
  18. if (this.propertiesFactory.isSet(ServerList.class, this.serviceId)) {
  19. return (ServerList)this.propertiesFactory.get(ServerList.class, config, this.serviceId);
  20. } else {
  21. DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(config, eurekaClientProvider);
  22. DomainExtractingServerList serverList = new DomainExtractingServerList(discoveryServerList, config, this.approximateZoneFromHostname);
  23. return serverList;
  24. }
  25. }
  26. ...
  27. }

在该类中配置了Ribbon Client 的 IPing 和 ServerList,在之前博文中我介绍了 Spring 为 Ribbon Client 提供了默认配置类,IPing 对应为 DummyPing,ServerList 对应为 ConfigurationBasedServerList。在这里可以看到,通过 PropertiesFactory 判断是否为该 Client 配置 IPing,若没有则生成 NIWSDiscoveryPing 作为默认配置。ServerList 同理,使用 DiscoveryEnabledNIWSServerList 作为默认配置。

LoadBalancerFeignClient.execute

在配置完成后,进入 Feign 的请求流程,通过 SynchronousMethodHandler 代理到 LoadBalancerFeignClient 的 execute 方法。

  1. public Response execute(Request request, Options options) throws IOException {
  2. try {
  3. URI asUri = URI.create(request.url());
  4. String clientName = asUri.getHost();
  5. URI uriWithoutHost = cleanUrl(request.url(), clientName);
  6. RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost);
  7. IClientConfig requestConfig = this.getClientConfig(options, clientName);
  8. return ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
  9. } catch (ClientException var8) {
  10. IOException io = this.findIOException(var8);
  11. if (io != null) {
  12. throw io;
  13. } else {
  14. throw new RuntimeException(var8);
  15. }
  16. }
  17. }

可以看到在该方法中,通过请求 url 获取对应的服务名,构建 RibbonRequest,IClientConfig 对象,最后通过 lbClient 方法创建对应 FeignLoadBalancer。

  1. private FeignLoadBalancer lbClient(String clientName) {
  2. return this.lbClientFactory.create(clientName);
  3. }

通过 CachingSpringLoadBalancerFactory 的 create 方法创建 FeignLoadBalancer。

  1. public FeignLoadBalancer create(String clientName) {
  2. FeignLoadBalancer client = (FeignLoadBalancer)this.cache.get(clientName);
  3. if (client != null) {
  4. return client;
  5. } else {
  6. IClientConfig config = this.factory.getClientConfig(clientName);
  7. ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
  8. ServerIntrospector serverIntrospector = (ServerIntrospector)this.factory.getInstance(clientName, ServerIntrospector.class);
  9. FeignLoadBalancer client = this.loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
  10. this.cache.put(clientName, client);
  11. return (FeignLoadBalancer)client;
  12. }
  13. }

在初次请求时会创建 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

  1. public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
  2. // 创建 Ribbon 的 Command 命令用来控制请求的执行流程
  3. LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
  4. try {
  5. // 用 lambda 表达式声明实现 ServerOperation 接口
  6. return command.submit(
  7. new ServerOperation<T>() {
  8. @Override
  9. public Observable<T> call(Server server) {
  10. // 获取到 server 后重构 URI
  11. URI finalUri = reconstructURIWithServer(server, request.getUri());
  12. S requestForServer = (S) request.replaceUri(finalUri);
  13. try {
  14. // 调用 FeignLoadBalancer 的 execute 方法执行请求
  15. return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
  16. }
  17. catch (Exception e) {
  18. return Observable.error(e);
  19. }
  20. }
  21. })
  22. .toBlocking()
  23. .single();
  24. } catch (Exception e) {
  25. Throwable t = e.getCause();
  26. if (t instanceof ClientException) {
  27. throw (ClientException) t;
  28. } else {
  29. throw new ClientException(e);
  30. }
  31. }
  32. }

通过源码可以看到,Ribbon 创建了 LoadBalancerCommand 对象,采用与 Hystrix 类似的命令模式来控制请求流程。
在 submit 方法中用 lambda 表达式声明了在获取到 server 后的处理流程,在该方法中调用了 FeignLoadBalancer 的 execute 方法。

selectServer

在 LoadBalancerCommand 中通过 selectServer 方法获取服务地址,本质是通过 ZoneAwareLoadBalancer 的 chooseServer 方法获取,根据 IRule 进行选择。

  1. private Observable<Server> selectServer() {
  2. return Observable.create(new OnSubscribe<Server>() {
  3. @Override
  4. public void call(Subscriber<? super Server> next) {
  5. try {
  6. Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
  7. next.onNext(server);
  8. next.onCompleted();
  9. } catch (Exception e) {
  10. next.onError(e);
  11. }
  12. }
  13. });
  14. }

execute

  1. public FeignLoadBalancer.RibbonResponse execute(FeignLoadBalancer.RibbonRequest request, IClientConfig configOverride) throws IOException {
  2. Options options;
  3. if (configOverride != null) {
  4. RibbonProperties override = RibbonProperties.from(configOverride);
  5. options = new Options(override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout));
  6. } else {
  7. options = new Options(this.connectTimeout, this.readTimeout);
  8. }
  9. Response response = request.client().execute(request.toRequest(), options);
  10. return new FeignLoadBalancer.RibbonResponse(request.getUri(), response);
  11. }

最后,通过 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 进行请求。

发表评论

表情:
评论列表 (有 0 条评论,249人围观)

还没有评论,来说两句吧...

相关阅读