• Feign源码解析5:loadbalancer


    背景#

    经过前面几篇的理解,我们大致梳理清楚了FeignClient的创建、Feign调用的大体流程,本篇会深入Feign调用中涉及的另一个重要组件:loadbalancer,了解loadbalancer在feign调用中的职责,再追溯其是如何创建的。

    在讲之前,我先提个重点,本文章的前期是引用了nacos依赖且开启了如下选项,启用了nacos的Loadbalancer:

    Copy
    spring.cloud.loadbalancer.nacos.enabled=true

    nacos的Loadbalancer是支持了基于nacos实例中的元数据进行服务实例筛选,比如权重等元数据。

    不开这个选项,则是用默认的Loadbalancer,不知道支不支持基于nacos实例中的元数据进行服务实例筛选(没测试)。

    我们这边是打开了这个选项,所以本文就基于打开的情况来讲。

    feign调用流程#

    大体流程#

    接上一篇文章,feign调用的核心代码如下:

    image-20240114113200793

    1处主要是封装请求;

    2处主要是依靠loadbalancer获取最终要调用的实例。

    但是在1和2之间,有一段代码是,获取LoadBalancerLifecycle类型的bean列表,大家看到什么lifecycle之类的名字,大概能知道,这些类是一些listener类,一般包含了几个生命周期相关的方法,比如这里就是:

    Copy
    void onStart(Request<RC> request); void onStartRequest(Request<RC> request, Response<T> lbResponse); void onComplete(CompletionContext<RES, T, RC> completionContext);

    这几个方法分别就是在loadbalancer的不同阶段进行调用。

    比如,我举个例子,我之前发现feign的日志里没打印最终调用的实例的ip、端口,导致查日志不方便,所以我就定义了一个自定义的LoadBalancerLifecycle类,将最终选择的实例的ip端口打印出来。

    image-20240114113841901

    我们看下,这里是如何获取LoadBalancerLifecycle对象的?

    Copy
    loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class)

    工厂用途#

    loadBalancerClientFactory这个字段,类型为LoadBalancerClientFactory,其定义:

    Copy
    public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>

    再看其注释:

    Copy
    A factory that creates client, load balancer and client configuration instances. It creates a Spring ApplicationContext per client name, and extracts the beans that it needs from there.

    这里就直说了,这是个工厂,它会给每个client创建一个spring容器。这里的client是啥呢,其实是org.springframework.cloud.client.loadbalancer.LoadBalancerClient类型的对象,它是在spring-cloud-commons中定义的接口:

    image-20240114115218070

    工厂自身的创建#

    工厂本身是自动装配的:

    image-20240114121947011

    看上图,需要一个构造函数参数,这个就是一些配置:

    image-20240114122040282

    调用的构造函数逻辑如下:

    Copy
    public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification> public static final String NAMESPACE = "loadbalancer"; public static final String PROPERTY_NAME = NAMESPACE + ".client.name"; public LoadBalancerClientFactory(LoadBalancerClientsProperties properties) { super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME); this.properties = properties; }

    这里调用了父类构造函数,把几个值存到父类中:

    Copy
    private final String propertySourceName; private final String propertyName; private Class<?> defaultConfigType; public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) { this.defaultConfigType = defaultConfigType; this.propertySourceName = propertySourceName; this.propertyName = propertyName; }

    完成构造后,我们发现,还调用了:

    Copy
    clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));

    这里的configurations类型是:

    Copy
    private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations;

    image-20240114122524436

    这个字段本身是通过构造函数方式注入的,来源呢,就是spring 容器。

    我们有必要探究下,这个LoadBalancerClientSpecification类型的bean,是怎么进入spring 容器的?

    其实,这个类也是代表了一份LoadbalancerClient的配置,之前feignClient也是一样的:

    Copy
    public class LoadBalancerClientSpecification implements NamedContextFactory.Specification { private String name; private Class<?>[] configuration; }

    这种类型的bean,其实是通过LoadBalancerClient注解和LoadBalancerClients注解进入容器的,当你使用这两个注解时,其实是支持配置一个class:

    image-20240114123017911

    image-20240114123039013

    然后,它们两注解都import了一个LoadBalancerClientConfigurationRegistrar类:

    image-20240114122913148

    这个会负责将对应的配置class,注册到容器中:

    image-20240114123306881

    注册时,name会有所区别,如果是LoadBalancerClients注解引入的,会加个default前缀。

    image-20240114123447968

    在默认情况下(引入了nacos-discovery、spring-cloud-loadbalancer的情况下),就会在代码中如下三处有@LoadBalancerClients注解:

    image-20240114124649366

    image-20240114124730849

    image-20240114124757216

    所以,我们工厂创建时debug,可以看到如下场景:

    image-20240114124936926

    从工厂获取LoadBalancerLifecycle#

    上面讲完了工厂的创建,这里回到工厂的使用。我们之前看到,会获取LoadBalancerLifecycle这种bean:

    Copy
    loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),

    但奇怪的是,获取bean不应该先用loadBalancerClientFactory创建的给各个loadBalancerClient的spring容器;再从容器获取bean吗?

    这里是简化了,直接让工厂负责全部事务,我要bean的时候,只找工厂要,工厂内部自己再去创建spring容器那些。

    所以我们看到,工厂是实现了接口:

    Copy
    public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification> implements ReactiveLoadBalancer.Factory<ServiceInstance>

    这个接口就有如下方法,这是个泛型方法:

    Copy
    Allows accessing beans registered within client-specific LoadBalancer contexts. <X> Map<String, X> getInstances(String name, Class<X> type);

    下面就看看方法如何实现的:

    image-20240114125611955

    这里就是分了两步,先获取容器,再从容器获取bean。

    创建容器#

    这个获取容器是先从缓存map获取,没有则创建。

    image-20240114125754859

    我们这里自然是没有的,进入createContext:

    image-20240114130159006

    这里首先是创建了一个spring上下文,里面是有一个bean容器的,容器里要放什么bean呢,首先就是上图中的configurations中那些LoadBalancerClient注解里指定的配置类,再然后,就是LoadBalancerClients注解里指定的那些默认的配置类,我们这里有3处LoadBalancerClients注解,但是只有nacos那一个,指定了配置类:

    Copy
    @LoadBalancerClients(defaultConfiguration = NacosLoadBalancerClientConfiguration.class) public class LoadBalancerNacosAutoConfiguration {

    所以,这里会把NacosLoadBalancerClientConfiguration这个配置类注册到容器。

    接下来,是如下这行:

    Copy
    context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);

    这里的defaultConfigType是啥呢,其实就是创建工厂时,指定的LoadBalancerClientConfiguration:

    image-20240114130737961

    到这里为止,基本spring容器该手工放入的bean就这些了。但这个容器内到时候只会有这些bean吗,不是的。

    因为我们这里放进去的几个bean,内部又定义了更多的bean。

    Copy
    nacosLoadBalancerClientConfiguration loadBalancerClientConfiguration

    nacosLoadBalancerClientConfiguration#

    首先是自动装配一个NacosLoadBalancer(在缺少这种ReactorLoadBalancer bean的情况下)

    image-20240114131703086

    再下来,会自动装配ServiceInstanceListSupplier bean:

    image-20240114131857085

    loadBalancerClientConfiguration#

    这边注意,也是在没注册这个bean的时候,自动装配ReactorLoadBalancer,这个其实会和上面的nacos的产生竞争,最终到底是哪个上岗呢,只能看顺序了:

    image-20240114132145192

    和nacos一样,自动装配ServiceInstanceListSupplier:

    image-20240114132231126

    竞争关系谁胜出#

    我们上面提到,nacos的配置类和spring-cloud-loadbalancer的配置类,是全面竞争的,最终的话,是谁胜出呢?

    我们看看容器完成bean创建后的情况:

    image-20240114133009841

    可以发现,是nacos的配置赢了。

    具体为什么赢,这个暂时不细说,基本就是bean的order那些事情。反正现在nacos赢了,看起来也没啥问题,我们就继续往后走,目前是完成了bean容器的创建。

    获取LoadBalancerLifecycle类型bean#

    我这个项目,并没定义这种bean,所以实际是取不到的,注意的是,在LoadbalancerClient对应的容器取不到,还是会去父容器取的。

    我们在父容器也没定义,所以最终是取不到。

    根据服务名获取最终实例#

    loadBalancerClient#

    目前准备分析如下代码:

    image-20240114133356196

    先看下这个字段来自于哪里:

    image-20240114141150266

    image-20240114141248933

    可以看出,来自于spring容器注入。

    image-20240114141418748

    所以,这里可以看出,loadBalancerClient类型为BlockingLoadBalancerClient。

    loadBalancerClient.choose#

    进入该方法:

    image-20240114141024073

    image-20240114141628863

    image-20240114141647797

    最终就是从容器获取,取到的就是nacos自动装配的NacosLoadBalancer:

    image-20240114141739653

    loadBalancer.choose#

    nacos这里的实现用的反应式编程,不怎么了解这块,反正最终是调用getInstanceResponse方法,且会把从nacos获取到的服务列表传递进来:

    image-20240114142341843

    image-20240114142555615

    可以看到,这里传入的就是实际的服务实例,还包含了nacos相关的元数据,如cluster、weight、是否临时、是否健康等。

    后续的逻辑就根据实例的各种属性进行筛选,如meta.nacos.cluster、ipv4/ipv6、

    image-20240114142817880

    根据权重进行选择:

    image-20240114143419421

    根据实例进行feign调用#

    image-20240114143627515

    我们跟进去后,发现主要就是feignClient.execute进行调用,在前后则是调用生命周期的相关方法:

    image-20240114143957771

    我们看到,这个client就是默认的FeignClient,比较原始,直接就是用原生的HttpURLConnection;我们之前文章提到,也是可以使用httpclient、okhttp那些feign.Client的实现,只要引入对应依赖即可。

    image-20240114144221192

    另外,这个也是没有连接池的,每次都是打开新连接;这里也用了外部options参数中的超时时间。

    image-20240114144501645

    后面的响应处理就略过不讲了。

    总结#

    我们总算是把大体流程都讲完了,下一篇讲讲我遇到的问题。




  • 相关阅读:
    Vite中的CSS工程化方案
    【附源码】计算机毕业设计JAVA研究生入学考试备考辅助系统
    Java--Spring事务
    Win11默认浏览器怎么设置?Win11设置默认浏览器的方法
    TypeScript中的几个方法
    无线传文件 - AirDroid - 电脑远程安装apk到手机
    Java高并发编程卷二(二) 锁
    python提高运算速度的方法:内存缓存+磁盘缓存
    10. Java异常(Exception)
    【安全】容器中二进制漏洞检测方案
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/17963745