Feign源码解析5:loadbalancer
背景
经过前面几篇的理解,我们大致梳理清楚了FeignClient的创建、Feign调用的大体流程,本篇会深入Feign调用中涉及的另一个重要组件:loadbalancer,了解loadbalancer在feign调用中的职责,再追溯其是如何创建的。
在讲之前,我先提个重点,本文章的前期是引用了nacos依赖且开启了如下选项,启用了nacos的Loadbalancer:
spring.cloud.loadbalancer.nacos.enabled=true
nacos的Loadbalancer是支持了基于nacos实例中的元数据进行服务实例筛选,比如权重等元数据。
不开这个选项,则是用默认的Loadbalancer,不知道支不支持基于nacos实例中的元数据进行服务实例筛选(没测试)。
我们这边是打开了这个选项,所以本文就基于打开的情况来讲。
feign调用流程
大体流程
接上一篇文章,feign调用的核心代码如下:
1处主要是封装请求;
2处主要是依靠loadbalancer获取最终要调用的实例。
但是在1和2之间,有一段代码是,获取LoadBalancerLifecycle类型的bean列表,大家看到什么lifecycle之类的名字,大概能知道,这些类是一些listener类,一般包含了几个生命周期相关的方法,比如这里就是:
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端口打印出来。
我们看下,这里是如何获取LoadBalancerLifecycle对象的?
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class)
工厂用途
loadBalancerClientFactory这个字段,类型为LoadBalancerClientFactory,其定义:
public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
再看其注释:
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中定义的接口:
工厂自身的创建
工厂本身是自动装配的:
看上图,需要一个构造函数参数,这个就是一些配置:
调用的构造函数逻辑如下:
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;
}
这里调用了父类构造函数,把几个值存到父类中:
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;
}
完成构造后,我们发现,还调用了:
clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));
这里的configurations类型是:
private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations;
这个字段本身是通过构造函数方式注入的,来源呢,就是spring 容器。
我们有必要探究下,这个LoadBalancerClientSpecification类型的bean,是怎么进入spring 容器的?
其实,这个类也是代表了一份LoadbalancerClient的配置,之前feignClient也是一样的:
public class LoadBalancerClientSpecification implements NamedContextFactory.Specification {
private String name;
private Class<?>[] configuration;
}
这种类型的bean,其实是通过LoadBalancerClient注解和LoadBalancerClients注解进入容器的,当你使用这两个注解时,其实是支持配置一个class:
然后,它们两注解都import了一个LoadBalancerClientConfigurationRegistrar类:
这个会负责将对应的配置class,注册到容器中:
注册时,name会有所区别,如果是LoadBalancerClients注解引入的,会加个default
前缀。
在默认情况下(引入了nacos-discovery、spring-cloud-loadbalancer的情况下),就会在代码中如下三处有@LoadBalancerClients注解:
所以,我们工厂创建时debug,可以看到如下场景:
从工厂获取LoadBalancerLifecycle
上面讲完了工厂的创建,这里回到工厂的使用。我们之前看到,会获取LoadBalancerLifecycle这种bean:
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
但奇怪的是,获取bean不应该先用loadBalancerClientFactory创建的给各个loadBalancerClient的spring容器;再从容器获取bean吗?
这里是简化了,直接让工厂负责全部事务,我要bean的时候,只找工厂要,工厂内部自己再去创建spring容器那些。
所以我们看到,工厂是实现了接口:
public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
implements ReactiveLoadBalancer.Factory<ServiceInstance>
这个接口就有如下方法,这是个泛型方法:
Allows accessing beans registered within client-specific LoadBalancer contexts.
<X> Map<String, X> getInstances(String name, Class<X> type);
下面就看看方法如何实现的:
这里就是分了两步,先获取容器,再从容器获取bean。
创建容器
这个获取容器是先从缓存map获取,没有则创建。
我们这里自然是没有的,进入createContext:
这里首先是创建了一个spring上下文,里面是有一个bean容器的,容器里要放什么bean呢,首先就是上图中的configurations中那些LoadBalancerClient注解里指定的配置类,再然后,就是LoadBalancerClients注解里指定的那些默认的配置类,我们这里有3处LoadBalancerClients注解,但是只有nacos那一个,指定了配置类:
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerClientConfiguration.class)
public class LoadBalancerNacosAutoConfiguration {
所以,这里会把NacosLoadBalancerClientConfiguration这个配置类注册到容器。
接下来,是如下这行:
context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
这里的defaultConfigType是啥呢,其实就是创建工厂时,指定的LoadBalancerClientConfiguration:
到这里为止,基本spring容器该手工放入的bean就这些了。但这个容器内到时候只会有这些bean吗,不是的。
因为我们这里放进去的几个bean,内部又定义了更多的bean。
nacosLoadBalancerClientConfiguration
loadBalancerClientConfiguration
nacosLoadBalancerClientConfiguration
首先是自动装配一个NacosLoadBalancer(在缺少这种ReactorLoadBalancer bean的情况下)
再下来,会自动装配ServiceInstanceListSupplier bean:
loadBalancerClientConfiguration
这边注意,也是在没注册这个bean的时候,自动装配ReactorLoadBalancer,这个其实会和上面的nacos的产生竞争,最终到底是哪个上岗呢,只能看顺序了:
和nacos一样,自动装配ServiceInstanceListSupplier:
竞争关系谁胜出
我们上面提到,nacos的配置类和spring-cloud-loadbalancer的配置类,是全面竞争的,最终的话,是谁胜出呢?
我们看看容器完成bean创建后的情况:
可以发现,是nacos的配置赢了。
具体为什么赢,这个暂时不细说,基本就是bean的order那些事情。反正现在nacos赢了,看起来也没啥问题,我们就继续往后走,目前是完成了bean容器的创建。
获取LoadBalancerLifecycle类型bean
我这个项目,并没定义这种bean,所以实际是取不到的,注意的是,在LoadbalancerClient对应的容器取不到,还是会去父容器取的。
我们在父容器也没定义,所以最终是取不到。
根据服务名获取最终实例
loadBalancerClient
目前准备分析如下代码:
先看下这个字段来自于哪里:
可以看出,来自于spring容器注入。
所以,这里可以看出,loadBalancerClient类型为BlockingLoadBalancerClient。
loadBalancerClient.choose
进入该方法:
最终就是从容器获取,取到的就是nacos自动装配的NacosLoadBalancer:
loadBalancer.choose
nacos这里的实现用的反应式编程,不怎么了解这块,反正最终是调用getInstanceResponse方法,且会把从nacos获取到的服务列表传递进来:
可以看到,这里传入的就是实际的服务实例,还包含了nacos相关的元数据,如cluster、weight、是否临时、是否健康等。
后续的逻辑就根据实例的各种属性进行筛选,如meta.nacos.cluster、ipv4/ipv6、
根据权重进行选择:
根据实例进行feign调用
我们跟进去后,发现主要就是feignClient.execute进行调用,在前后则是调用生命周期的相关方法:
我们看到,这个client就是默认的FeignClient,比较原始,直接就是用原生的HttpURLConnection;我们之前文章提到,也是可以使用httpclient、okhttp那些feign.Client的实现,只要引入对应依赖即可。
另外,这个也是没有连接池的,每次都是打开新连接;这里也用了外部options参数中的超时时间。
后面的响应处理就略过不讲了。
总结
我们总算是把大体流程都讲完了,下一篇讲讲我遇到的问题。
Feign源码解析5:loadbalancer的更多相关文章
- Feign源码解析
1. Feign源码解析 1.1. 启动过程 1.1.1. 流程图 1.1.2. 解释说明 Feign解析过程依赖Spring的初始化,它通过实现ImportBeanDefinitionRegistr ...
- Feign源码解析系列-注册套路
感谢不知名朋友的打赏,感谢你的支持! 开始 在追寻Feign源码的过程中发现了一些套路,既然是套路,就可以举一反三,所以值得关注. 这篇会详细解析Feign Client配置和初始化的方式,这些方式大 ...
- Feign源码解析系列-那些注解们
开始 Feign在Spring Cloud体系中被整合进来作为web service客户端,使用HTTP请求远程服务时能就像调用本地方法,可见在未来一段时间内,大多数Spring Cloud架构的微服 ...
- Feign源码解析系列-最佳实践
前几篇准备写完feign的源码,这篇直接给出Feign的最佳实践,考虑到目前网上还没有一个比较好的实践解释,对于新使用spring cloud的同学会对微服务之间的依赖产生一些迷惑,也会走一些弯路.这 ...
- Feign源码解析系列-核心初始化
开始 初始化Feign客户端当然是整个过程中的核心部分,毕竟初始化完毕就等着调用了,初始化时候准备的什么,流程就走什么. 内容 从上一篇中,我们已经知道,对于扫描到的每一个有@FeignClient, ...
- Feign 系列(05)Spring Cloud OpenFeign 源码解析
Feign 系列(05)Spring Cloud OpenFeign 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/ ...
- Feign 系列(04)Contract 源码解析
Feign 系列(04)Contract 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/11563952.html# ...
- Hystrix源码解析
1. Hystrix源码解析 1.1. @HystrixCommand原理 直接通过Aspect切面来做的 1.2. feign hystrix原理 它的本质原理就是对HystrixCommand的动 ...
- SpringCloud服务调用源码解析汇总
相信我,你会收藏这篇文章的,本篇文章涉及Ribbon.Hystrix.Feign三个组件的源码解析 Ribbon架构剖析 这篇文章介绍了Ribbon的基础架构,也就是下图涉及到的6大组件: Ribbo ...
- Spring Boot @Enable*注解源码解析及自定义@Enable*
Spring Boot 一个重要的特点就是自动配置,约定大于配置,几乎所有组件使用其本身约定好的默认配置就可以使用,大大减轻配置的麻烦.其实现自动配置一个方式就是使用@Enable*注解,见其名知 ...
随机推荐
- Linux系列教程——Linux文件编辑、Linux用户管理
@ 目录 1 Linux基本权限 1.权限基本概述 1.什么是权限? 2.为什么要有权限? 3.权限与用户之间的关系? 4.权限中的rwx分别代表什么含义? 2.权限设置示例 1.为什么要设定权限,我 ...
- 第一个 Go 程序"hello,world" 与 main 函数和Go常用基本命令
第一个 Go 程序"hello,world" 与 main 函数和Go常用基本命令 目录 第一个 Go 程序"hello,world" 与 main 函数和Go ...
- 掌握Go类型内嵌:设计模式与架构的新视角
本文深入探讨了Go语言中的类型内嵌特性,从基础概念到实际应用,以及相关的最佳实践.文章不仅讲解了如何在Go中实现和使用类型内嵌,还通过具体的代码示例展示了其应用场景和潜在陷阱.最后,文章总结了类型内嵌 ...
- DHorse v1.4.2 发布,基于 k8s 的发布平台
版本说明 优化特性 在集群列表增加集群版本: 修改Jvm的GC指标名: 解决问题 解决shell脚本换行符的问题: 解决部署历史列表页,环境名展示错误的问题: 解决指标收集功能的异常: 升级指南 升级 ...
- MIT协议原文及中文翻译
MIT协议原文及翻译 参考链接 原文: Copyright ( C ) Permission is hereby granted, free of charge, to any person obta ...
- FreeSWITCH添加自定义endpoint之api及app开发
操作系统 :CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 之前写过FreeSWITCH添加自定义endpoint的文章,今天整理下api及app开发的笔记.历史文章可参考如下 ...
- JS深入之内存详解,数据结构,存储方式
理解了本文,就知道深拷贝和浅拷贝的底层,了解赋值的底层原理. 可以结合另一篇文章一起食用:深拷贝与浅拷贝的区别,实现深拷贝的方法介绍. 以下是正文: 栈数据结构 栈的结构就是后进先出(LIFO),如果 ...
- html笔记重点
第五周-周二 一.视频和音频 <video src="路径" controls="controls"></video> 1.加contr ...
- 文心一言 VS 讯飞星火 VS chatgpt (132)-- 算法导论11.2 4题
四.用go语言,说明在散列表内部,如何通过将所有未占用的槽位链接成一个自由链表,来分配和释放元素所占的存储空间.假定一个槽位可以存储一个标志.一个元素加上一个或两个指针.所有的字典和自由链表操作均应具 ...
- 玩转开源 |Hugo 的使用实践
Hugo 是一个能够以出色速度构建静态网页的工具,它为我们提供了极具灵活性的平台,可以塑造成符合个人需求的网页.在上一篇博文中已经介绍了 Hugo 的基本搭建步骤,那如何使用 Hugo 搭建符合自己需 ...