likes
comments
collection
share

OpenFeign最核心组件LoadBalancerFeignClient详解(集成Ribbon负载均衡能力)

作者站长头像
站长
· 阅读数 32

前言

上一篇文章讲解了Feign第一个可扩展组件Client,feign.Client默认实现是feign.Client.Default,他使用了HttpURLConnection作为通信组件,如果是在微服务的使用场景下,默认的Client缺乏负载均衡的能力,使用场景有限,因此Spring Cloud团队进行了扩展,开发了具备通信能力和负载均衡能力的LoadBalancerFeignClient,由于负载均衡能力默认使用ribbon提供,因此本文单独分析负载均衡能力如何和OpenFeign集成的。

下面我们开始介绍今天的主角 LoadBalancerFeignClient

OpenFeign最核心组件LoadBalancerFeignClient详解(集成Ribbon负载均衡能力)

开车之前,先把整理的LoadBalancerFeignClient主流程时序图贴出来,逻辑还是非常清晰的,看完整篇文章再来看这个图,应该会更明白了

OpenFeign最核心组件LoadBalancerFeignClient详解(集成Ribbon负载均衡能力)

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最核心组件LoadBalancerFeignClient详解(集成Ribbon负载均衡能力)

第二个点是openfeign为每一个服务提供者创建一个负载均衡客户端,相互隔离。

FeignLoadBalancer执行流程

FeignLoadBalancer创建完后在哪用呢?其实就是在LoadBalancerFeignClient中会有使用,我们开始看LoadBalancerFeignClient的execute执行请求流程,在execute方法中,我们可以清晰的看到发起通信的主流程,里面就有一步获取FeignLoadBalancer。

OpenFeign最核心组件LoadBalancerFeignClient详解(集成Ribbon负载均衡能力)

看下源码,确实和我们平时发起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发起通信的主体流程通过下方时序图表示出来,还是非常清晰的

OpenFeign最核心组件LoadBalancerFeignClient详解(集成Ribbon负载均衡能力)

总结

本文分析了LoadBalancerFeignClient整体的执行流程,里面涉及到Rule组件没有详细分析,Rule在ribbon中是比较重要的一个组件,他可以从配置文件或者注册中心读取配置,内容会占用比较多的篇幅,避免阅读体验不好,本文不详细剖析,下文将补充这块的内容,咱们下篇文章接着分析Rule对象在负载均衡中的实现,不见不散哦。

OpenFeign最核心组件LoadBalancerFeignClient详解(集成Ribbon负载均衡能力)

转载自:https://juejin.cn/post/7245613765692653625
评论
请登录