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

参考资料:Spring Cloud 官网

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

# 回顾

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 Support

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

  • feign.Client.Default

  • org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient

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

# LoadBalancerFeignClient 的注册

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 进行了实例化配置。

# LoadBalancerFeignClient 客户端负载均衡

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

# 分析 LoadBalancerFeignClient#execute

# 请求参数分析

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

其中参数 Request 数据结构如下

FeignClient 代理对象获取到的MethodHandler信息

Request.Options 为 Ribbon 请求配置信息

# 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 等 进行服务实例的获取。

精彩内容推送,请关注公众号!
最近更新时间: 4/22/2020, 7:23:03 PM