Spring Cloud Feign【源码篇】Feign 如何调用Ribbon进行客户端负载均衡

参考资料: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 选择 Ribbon 作为负载均衡客户端

查看 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 。

LoadBalancerFeignClient 如何衔接 Ribbon 实现客户端负载均衡

前提:Feign Client 最终执行调用的是 Client#execute 方法,而此时 Client 实现类是 LoadBalancerFeignClient。

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

其中参数 Request 数据结构如下

Spring Cloud Feign【源码篇】Feign 如何调用Ribbon进行客户端负载均衡_第1张图片
而 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请求配置属性) 发起请求。

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

获取 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。

根据 RibbonRequest 和 IClientConfig 发起请求。

LoadBalancerFeignClient#execute return 执行的代码如下

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

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

  • LoadBalancerFeignClient#lbClient
  • FeignLoadBalancer#executeWithLoadBalancer

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

聚焦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() 方法主线逻辑:

  • 封装 RibbonReuqest 请求对象
  • 从容器中获取 Ribbon 请求配置参数信息 IConfigClient
  • 根据 RibbonRequest 和 IConfigClient 构造 FeignLoadBalancer 。
    3、FeignLoadBalancer 根据 Ribbon 的 IRule 规则从 Iloadbalancer 获取一个服务实例 server。
    4、FeignLoadBalancer#execute 根据获取到的服务实例 Server 发起请求调用。然后返回。

最后总结

首先我们再来回顾一下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 等 进行服务实例的获取。

精彩内容推送,请关注公众号!
Spring Cloud Feign【源码篇】Feign 如何调用Ribbon进行客户端负载均衡_第2张图片

你可能感兴趣的:(微信公众号同步)