Feign源码解析6:如何集成discoveryClient获取服务列表
背景
我们上一篇介绍了feign调用的整体流程,在@FeignClient
没有写死url的情况下,就会生成一个支持客户端负载均衡的LoadBalancerClient
。这个LoadBalancerClient
可以根据服务名,去获取服务对应的实例列表,然后再用一些客户端负载均衡算法,从这堆实例列表中选择一个实例,再进行http调用即可。
上图中,最核心的也就是2处。我们本次就从这里入手,去研究下,服务实例列表是如何获取到的,以及如何配置静态的服务实例地址。
服务实例列表相关bean初始化
在上图的2处开始执行前,有这么一行:
这里就会去查找bean,类型是LoadBalancerLifecycle.class。去哪里查找呢,spring容器,但是是各个loadbalancer自己的spring容器。
刚开始嘛,容器还没有,此时就会触发spring容器的创建和初始化。这个容器里有哪些bean呢?
主要的bean来源于LoadBalancerClientConfiguration这个配置类。里面包含了两个重要的bean,一个是loadbalancer,支持随机获取某个实例,但这个bean,可以从下面的代码看到,它的第一个构造参数,是去获取一个ServiceInstanceListSupplier类型的bean的provider,要靠这个provider提供服务实例列表。
所以,这个bean其实是依赖于ServiceInstanceListSupplier这种bean的。
下面这个则是ServiceInstanceListSupplier类型,也就是实例列表提供者。
ServiceInstanceListSupplierBuilder
ServiceInstanceListSupplier.builder():
static ServiceInstanceListSupplierBuilder builder() {
return new ServiceInstanceListSupplierBuilder();
}
这个就是普通的建造者,没有什么特别。接下来,则是给builder设置DiscoveryClient,这个就是服务发现相关的client,比如eureka、nacos这些的客户端:
.withBlockingDiscoveryClient()
这里我们发现一个一个箭头函数,这个箭头函数有一个入参,名字是context,然后return了一个DiscoveryClientServiceInstanceListSupplier类型的对象。
函数最终赋值给了:
private Creator baseCreator;
它的类型:
Allows creating a {@link ServiceInstanceListSupplier} instance based on provided
{@link ConfigurableApplicationContext}.
public interface Creator extends Function<ConfigurableApplicationContext, ServiceInstanceListSupplier> {
}
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
这个把参数带入,就是:
ServiceInstanceListSupplier apply(ConfigurableApplicationContext t);
也就是接受一个spring上下文参数,返回一个ServiceInstanceListSupplier类型的对象。
所以再看下图,也就是从spring中获取DiscoveryClient类型的bean,然后new一个DiscoveryClientServiceInstanceListSupplier类型的对象返回。
接下来,builder又设置了缓存:
ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching()
private DelegateCreator cachingCreator;
public interface DelegateCreator extends
BiFunction<ConfigurableApplicationContext, ServiceInstanceListSupplier, ServiceInstanceListSupplier> {
}
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
翻译后就是:
ServiceInstanceListSupplier apply(<ConfigurableApplicationContext t, ServiceInstanceListSupplier u);
这里其实不用说太细,无非是装饰器模式,又套了一层缓存。
接下来,进入最终的build环节:
可以看到,首先是执行了baseCreator,传入了spring上下文,此时就会触发之前看到的:
CompositeDiscoveryClient
我们上图中,获取到的DiscoveryClient类型的bean为CompositeDiscoveryClient。它就像它的名字一样,里面聚合了多个DiscoveryClient。
这个bean的定义在哪里呢?这是靠自动装配引入的:
这个聚合类依赖的discoveryClient哪里来的呢?
首先是nacosDiscoveryClient:
再一个是SimpleDiscoveryClient类型:
多个DiscoveryClient的顺序
在CompositeDiscoveryClient中,是用list维护各个DiscoveryClient。
private final List<DiscoveryClient> discoveryClients;
谁先谁后,重要吗?看看下面的方法,是用来获取服务实例的:
这里是先获取到则直接返回,说明还是很重要的。
各个DiscoveryClient的order值怎么获取呢?
public interface DiscoveryClient extends Ordered {
/**
* Default order of the discovery client.
*/
int DEFAULT_ORDER = 0;
...
default int getOrder() {
return DEFAULT_ORDER;
}
}
nacos中,实现了这个类,但是没有覆写getOrder,所以对于NacosDiscoveryClient,值就是0.
public class NacosDiscoveryClient implements DiscoveryClient
对于SimpleDiscoveryClient来说,我们先不管它是啥,我们看其类定义:
其支持从配置文件中获取order:
@Override
public int getOrder() {
return this.simpleDiscoveryProperties.getOrder();
}
你没有显示设置这个order属性的话,默认也是0.
所以,不显式设置SimpleDiscoveryProperties的order的话,SimpleDiscoveryClient和NacosDiscoveryClient的order值相同,那谁先谁后就难讲了,这块待细挖才知道。
SimpleDiscoveryClient
这个discoveryClient是干嘛的呢,没啥存在感?
其实它是用来从配置文件中获取服务实例的。
A DiscoveryClient that will use the properties file as a source of service instances.
它依赖的配置类如下:
public class SimpleDiscoveryClient implements DiscoveryClient {
private SimpleDiscoveryProperties simpleDiscoveryProperties;
public SimpleDiscoveryClient(SimpleDiscoveryProperties simpleDiscoveryProperties) {
this.simpleDiscoveryProperties = simpleDiscoveryProperties;
}
它可以配置各个Feign服务的服务实例,以及我们前面提到的order(通过把这里的order改小,可以排到nacosDiscoveryClient前面,达成屏蔽nacos中的服务实例的效果)
我们可以像下面这样来配置:
spring:
application:
discovery:
client:
simple:
instances:
echo-service-provider:
- uri: http://1.1.1.1:8082
metadata:
my: instance1
- uri: http://2.2.2.2:8082
metadata:
my: instance2
正常像上面这样就可以了,但是,nacos会排在它前面,导致无法生效:
所以,还得配上order:
spring:
application:
discovery:
client:
simple:
order: -1
instances:
echo-service-provider:
- uri: http://1.1.1.1:8082
metadata:
my: instance1
- uri: http://2.2.2.2:8082
metadata:
my: instance2
DiscoveryClientServiceInstanceListSupplier
构造好了前面的CompositeDiscoveryClient,我们就会开始创建服务实例supplier。
上图可以看到,这里有delegate.getInstances(serviceId)
,但后面又进行了封装,最终的类型是:
private final Flux<List<ServiceInstance>> serviceInstances;
这个Flux是反应式编程相关的api,不是很懂,但内部主要就是封装了一个数据源,等到需要获取服务实例的时候,就会真正调用到:
delegate.getInstances(serviceId)
届时,就会调用到:
缓存包装
这个DiscoveryClientServiceInstanceListSupplier,后续又经过cache相关包装,最终的类型是:
CachingServiceInstanceListSupplier
这个bean咱们就讲到这里。
reactorServiceInstanceLoadBalancer
接下来,开始看:
第一个参数:
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class)
public <T> ObjectProvider<T> getLazyProvider(String name, Class<T> type) {
return new ClientFactoryObjectProvider<>(this, name, type);
}
ClientFactoryObjectProvider(NamedContextFactory<?> clientFactory, String name, Class<T> type) {
this.clientFactory = clientFactory;
this.name = name;
this.type = type;
}
接下来构造这个随机的loadbalancer:
public RoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}
到此,就构造完成了。
此时,我们也基本完成了loadbalancer对应的整个spring容器的初始化。
loadBalancerClient.choose
完成了spring容器初始化后,接下来开始真正执行下图2处:
首先就是获取loadbalancer,就是从容器内获取ReactorServiceInstanceLoadBalancer类型的bean:
@Override
public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
}
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
try {
return context.getBean(type);
}
catch (NoSuchBeanDefinitionException e) {
// ignore
}
return null;
}
容器中,这种类型的bean,就只有前面讲的RoundRobinLoadBalancer.
然后调用loadBalancer.choose(request):
org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer#choose
public Mono<Response<ServiceInstance>> choose(Request request) {
// 1
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
// 2
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
1处就从容器中获取到前面提到的CachingServiceInstanceListSupplier。
2处的supplier.get(request)
:
然后对这个Flux<List<ServiceInstance>>
类型的对象,执行next,把当前对象变成了MonoNext类型的对象,MonoNext的注释是:Emits a single item at most from the source.
接下来是map操作,转成了一个MonoMap类型的对象:
这里还不会实际触发上面的客户端负载均衡逻辑,此时只是封装成了MonoMap:
把MonoMap丢给了如下的from函数,里面把MonoMap强转为了Mono类型:
接下来执行block操作,转为同步阻塞:
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
这里我感觉就是,创建了一个实际的订阅者,且这个订阅者订阅了当前这个MonoMap,所以这个MonoMap就得真正开始干活了(之前只是把一堆操作给封装进去了,但没有实际做)。
此时,也会真正触发如下地方:
这里完成后呢,就真正拿到了服务实例列表,此时,就会触发之前那个map函数:
根据当前loadbalancer的算法(随机算法),进行多个服务实例中选一个的操作:
接着,我们终于拿到了一个实例了,可以进行后续调用了:
总结
反应式编程,这个真是太难看懂了,实在是劝退。
今天是大寒,马上要更冷了,不过再坚持一阵,就能春暖花开了,兄弟们
参考
https://docs.spring.io/spring-cloud-commons/docs/3.1.8/reference/html/#zone-based-load-balancing
https://mp.weixin.qq.com/s/aRpwCtgENCwubMF3idQQzQ
Feign源码解析6:如何集成discoveryClient获取服务列表的更多相关文章
- 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# ...
- jQuery2.x源码解析(构建篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...
- Spring-cloud & Netflix 源码解析:Eureka 服务注册发现接口 ****
http://www.idouba.net/spring-cloud-source-eureka-client-api/?utm_source=tuicool&utm_medium=refer ...
- Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
概要 这一章,我们对TreeMap进行学习.我们先对TreeMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeMap.内容包括:第1部分 TreeMap介绍第2部分 TreeMa ...
随机推荐
- 简便实用:在 ASP.NET Core 中实现 PDF 的加载与显示
前言 在Web应用开发中,经常需要实现PDF文件的加载和显示功能.本文小编将为您介绍如何在ASP.NET Core中实现这一功能,以便用户可以在Web应用中查看和浏览PDF文件. 实现步骤 1)在服务 ...
- SpringBoot对象拷贝
目录 概述 定义实体类 Car size carInfo 造测试数据 Spring BeanUtils Apache BeanUtils Cglib BeanCopier MapStruct 性能测试 ...
- GPT-4多模态大型语言模型发布
GPT-4 模型是OpenAI开发的第四代大型语言模型(LLM),它将是一个多模态模型,会提供完全不同的可能性-例如文字转图像.音乐甚至视频.GPT 全称为 Generative Pre-traine ...
- 解决方案 | VS2022 + AutoCAD2024 + ObjectARX2024环境搭建过程
一.准备工具 1.vs2022 自行网络搜索,各种版本均可(比如专业版.社区版),注意使用社区版必须使用最新版,目前是17.8版本,否则最终会无法使用样板. 2.cad2024 自行网络搜索 3.Ob ...
- Python——第二章:字典的循环、嵌套、"解构"(解包)
字典进阶操作 -- 循环和嵌套 字典的循环 我们先看直接打印字典的样子,会分别对每对key:value进行打印,并使用,分隔他们 dic = { "赵四": "特别能歪嘴 ...
- 在centos7.9中 修改docker0 的网卡默认IP地址
docker0网卡的默认IP地址为172.17.0.1/16 因此很可能会与企业网中的业务地址冲突,为了解决这个问题,必须修改docker0的网卡配置,操作如下 1.查看网卡docker0的默认地址 ...
- 当创建pvc后,kubernetes组件如何协作
本文分享自华为云社区<当创建一个pvc后,kubernetes会发生什么?>,作者:可以交个朋友. 一.背景 外部存储接入 Kubernetes 的方式主要有两种:In-Tree 和 Ou ...
- QNX 性能分析工具
QNX 性能分析工具 小结 hogs:列出最占用 CPU/RAM 的进程,可以统计内存占用详细情况[可单个进程] pidin:显示进程信息.系统信息.库信息...[可单个进程] ps:显示进程信息[可 ...
- 一文带你了解GaussDB(DWS) 的Roach逻辑备份实现原理
摘要:Roach工具是GaussDB(DWS)推出的一款主力的备份恢复工具,包含物理与逻辑备份两种主要能力,本文着重于讲解Roach逻辑备份的实现原理. 一.简介 在大数据时代,数据的完整和可靠性成为 ...
- JPEG/Exif/TIFF格式解读(4):win10照片旋转win7不识别
xif元数据根据不同的内容分布在五个不同的IFD中. IFD0中的数据是由TIFF定义的基本图像数据,其中有些与照片无关,所以Exif只实现其中一小部分.这部份数据在Photoshop中称为TIFF元 ...