OpenFeign最核心组件LoadBalancerFeignClient详解(集成Ribbon负载均衡能力)
前言
上一篇文章讲解了Feign第一个可扩展组件Client,feign.Client默认实现是feign.Client.Default,他使用了HttpURLConnection作为通信组件,如果是在微服务的使用场景下,默认的Client缺乏负载均衡的能力,使用场景有限,因此Spring Cloud团队进行了扩展,开发了具备通信能力和负载均衡能力的LoadBalancerFeignClient
,由于负载均衡能力默认使用ribbon提供,因此本文单独分析负载均衡能力如何和OpenFeign集成的。
下面我们开始介绍今天的主角
LoadBalancerFeignClient
开车之前,先把整理的LoadBalancerFeignClient主流程时序图贴出来,逻辑还是非常清晰的,看完整篇文章再来看这个图,应该会更明白了
LoadBalancerFeignClient初探
我们先可以看到LoadBalancerFeignClient
的构造函数
public class LoadBalancerFeignClient implements Client {
static final Request.Options DEFAULT_OPTIONS = new Request.Options();
//通信客户端
private final Client delegate;
//负载均衡客户端工厂
private CachingSpringLoadBalancerFactory lbClientFactory;
//Spring工厂
private SpringClientFactory clientFactory;
public LoadBalancerFeignClient(Client delegate,
CachingSpringLoadBalancerFactory lbClientFactory,
SpringClientFactory clientFactory) {
this.delegate = delegate;
this.lbClientFactory = lbClientFactory;
this.clientFactory = clientFactory;
}
}
除了拥有feign.Client的引用外,还有1个和负载均衡相关的参数
CachingSpringLoadBalancerFactory
,这个负载均衡客户端工厂在哪创建的呢?
在openfeign包中有个自动装配类,我们可以找到答案
package org.springframework.cloud.openfeign.ribbon;
/**
* Autoconfiguration to be activated if Feign is in use and needs to be use Ribbon as a
* load balancer.
*/
@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
@Primary
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryFactory);
}
}
CachingSpringLoadBalancerFactory里面有一个最核心的方法,就是创建 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;
}
}
这里有两个比较有意思的设计,Spring框架源码中比较常见:
第一个是CachingSpringLoadBalancerFactory创建完FeignLoadBalancer
之后,就会存入map缓存,避免二次构建。
volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap();
key是服务提供者名称,value为FeignLoadBalancer
或者RetryableFeignLoadBalancer
第二个点是openfeign为每一个服务提供者创建一个负载均衡客户端,相互隔离。
FeignLoadBalancer执行流程
FeignLoadBalancer创建完后在哪用呢?其实就是在LoadBalancerFeignClient中会有使用,我们开始看LoadBalancerFeignClient的execute执行请求流程,在execute方法中,我们可以清晰的看到发起通信的主流程,里面就有一步获取FeignLoadBalancer。
看下源码,确实和我们平时发起http通信的流程非常相似
public Response execute(Request request, Request.Options options) throws IOException {
//构建url
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
//将url剔除服务名字
//http://127.0.0.1:8080/test/feign==>
//http:///test/feign
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//创建请求对象
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);
//请求配置
IClientConfig requestConfig = this.getClientConfig(options, clientName);
//构建FeignLoadBalancer并发起请求
return ((FeignLoadBalancer.RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
}
发起通信流程和我们平时写http请求的流程如出一辙,LoadBalancerFeignClient将请求发起的动作委托给了FeignLoadBalancer
,也就是包了一层,和我们平台写的facade模式一样。
接下来我们需要分析FeignLoadBalancer的执行流程,FeignLoadBalancer继承了抽象类
AbstractLoadBalancerAwareClient
,AbstractLoadBalancerAwareClient.executeWithLoadBalancer里面是一个模版方法设计模式的体现,它专门负责负载均衡的逻辑,而通信逻辑由子类实现,FeignLoadBalancer就是是其子类,负责发起请求通信。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
//通过负载均衡能力,获取服务
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
//负载均衡选择的服务信息里拿到ip,port,
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
//发起通信
return AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig);
}
catch (Exception e) {
return Observable.error(e);
}
}
从getServerFromLoadBalancer这个方法名很容易看出来,他是通过负载均衡器获取服务。
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
Server svc = lb.chooseServer(loadBalancerKey);
return svc;
}
最终通过Rule对象选择一个服务
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
return rule.choose(key);
}
Rule模块的实现内容比较,咱们先不看,我们接着看AbstractLoadBalancerAwareClient.executeWithLoadBalancer 发起通信的主流程, reconstructURIWithServer方法则是根据负载均衡返回的Server信息重构请求url,逻辑非常清晰。
public URI reconstructURIWithServer(Server server, URI original) {
//从服务信息获取ip,port,协议
String host = server.getHost();
int port = server.getPort();
String scheme = server.getScheme();
try {
StringBuilder sb = new StringBuilder();
//拼接协议
sb.append(scheme).append("://");
//拼接ip
sb.append(host);
//拼接端口
sb.append(":").append(port);
//拼接路径
sb.append(original.getRawPath());
//构建URL
URI newURI = new URI(sb.toString());
return newURI;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
到这里请求url已经组装完整了,可以发起请求了,这个时候通过execute方法执行请求
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException {
Request.Options options;
//将ribbon的配置覆盖feign请求配置覆盖
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options((long)override.connectTimeout(this.connectTimeout), TimeUnit.MILLISECONDS, (long)override.readTimeout(this.readTimeout), TimeUnit.MILLISECONDS, override.isFollowRedirects(this.followRedirects));
} else {
options = new Request.Options((long)this.connectTimeout, TimeUnit.MILLISECONDS, (long)this.readTimeout, TimeUnit.MILLISECONDS, this.followRedirects);
}
//调用通信客户端发起请求
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
再次把LoadBalancerFeignClient发起通信的主体流程通过下方时序图表示出来,还是非常清晰的
总结
本文分析了LoadBalancerFeignClient整体的执行流程,里面涉及到Rule组件没有详细分析,Rule在ribbon中是比较重要的一个组件,他可以从配置文件或者注册中心读取配置,内容会占用比较多的篇幅,避免阅读体验不好,本文不详细剖析,下文将补充这块的内容,咱们下篇文章接着分析Rule对象在负载均衡中的实现,不见不散哦。
转载自:https://juejin.cn/post/7245613765692653625