背景

我们上一篇介绍了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获取服务列表的更多相关文章

  1. Feign源码解析

    1. Feign源码解析 1.1. 启动过程 1.1.1. 流程图 1.1.2. 解释说明 Feign解析过程依赖Spring的初始化,它通过实现ImportBeanDefinitionRegistr ...

  2. Feign源码解析系列-注册套路

    感谢不知名朋友的打赏,感谢你的支持! 开始 在追寻Feign源码的过程中发现了一些套路,既然是套路,就可以举一反三,所以值得关注. 这篇会详细解析Feign Client配置和初始化的方式,这些方式大 ...

  3. Feign源码解析系列-那些注解们

    开始 Feign在Spring Cloud体系中被整合进来作为web service客户端,使用HTTP请求远程服务时能就像调用本地方法,可见在未来一段时间内,大多数Spring Cloud架构的微服 ...

  4. Feign源码解析系列-最佳实践

    前几篇准备写完feign的源码,这篇直接给出Feign的最佳实践,考虑到目前网上还没有一个比较好的实践解释,对于新使用spring cloud的同学会对微服务之间的依赖产生一些迷惑,也会走一些弯路.这 ...

  5. Feign源码解析系列-核心初始化

    开始 初始化Feign客户端当然是整个过程中的核心部分,毕竟初始化完毕就等着调用了,初始化时候准备的什么,流程就走什么. 内容 从上一篇中,我们已经知道,对于扫描到的每一个有@FeignClient, ...

  6. Feign 系列(05)Spring Cloud OpenFeign 源码解析

    Feign 系列(05)Spring Cloud OpenFeign 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/ ...

  7. Feign 系列(04)Contract 源码解析

    Feign 系列(04)Contract 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/11563952.html# ...

  8. jQuery2.x源码解析(构建篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...

  9. Spring-cloud & Netflix 源码解析:Eureka 服务注册发现接口 ****

    http://www.idouba.net/spring-cloud-source-eureka-client-api/?utm_source=tuicool&utm_medium=refer ...

  10. Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例

    概要 这一章,我们对TreeMap进行学习.我们先对TreeMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用TreeMap.内容包括:第1部分 TreeMap介绍第2部分 TreeMa ...

随机推荐

  1. 3D 高斯喷溅 🤗 为什么图形永远不会相同

    高斯喷溅 (Gaussian Splatting) 技术是一种翻天覆地的渲染手段,能够以 144 帧每秒的速度渲染出高质量的场景,这和传统的图形处理流程截然不同 这种将高斯数据转换成图像的过程,与训练 ...

  2. 华企盾DSC由于半透明软件设置了需要管理员权限打开导致半透明打不开加密文件

    解决方法: 1.右键该应用程序->属性->兼容性,去掉[以管理员权限运行此程序] 2.也可以打开控制面板->系统和安全->用户账户控制设置调至最低

  3. 一篇可供参考的 K8S 落地实践经验

    前言 k8s 即 Kubernetes,是一个开源的容器编排引擎,用来对容器化应用进行自动化部署. 扩缩和管理 本篇文章将分享 k8s v1.18.8 的安装,以及其面板,监控,部署服务,使用Ingr ...

  4. Bert-vits2最终版Bert-vits2-2.3云端训练和推理(Colab免费GPU算力平台)

    对于深度学习初学者来说,JupyterNoteBook的脚本运行形式显然更加友好,依托Python语言的跨平台特性,JupyterNoteBook既可以在本地线下环境运行,也可以在线上服务器上运行.G ...

  5. Shiro 的基本使用

    简介 Apache Shiro 是一个强大的.灵活的开源安全框架,可以干净地处理验证.授权.企业会话管理和加密等功能 相关特性 Apache Shiro 具有的主要特性如下图所示: 主要关注的地方在于 ...

  6. 【scikit-learn基础】--『监督学习』之 决策树分类

    决策树分类算法是一种监督学习算法,它的基本原理是将数据集通过一系列的问题进行拆分,这些问题被视为决策树的叶子节点和内部节点.决策树的每个分支代表一个可能的决策结果,而每个叶子节点代表一个最终的分类结果 ...

  7. three.js中场景模糊、纹理失真的问题

    目录 1. 概述 2. 方案 2.1. 开启反走样 2.2. 开启HiDPI设置 3. 结果 4. 参考 1. 概述 在three.js场景中,有时会遇到场景模糊,纹理失真的现象,似乎three.js ...

  8. 兼容并蓄广纳百川,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang复合容器类型的声明和使用EP04

    书接上回,容器数据类型是指一种数据结构.或者抽象数据类型,其实例为其他类的对象. 或者说得更具体一点,它是以一种遵循特定访问规则的方法来存储对象. 容器的大小取决于其包含的基础数据对象(或数据元素)的 ...

  9. 小熊派开发实践丨漫谈LiteOS之传感器移植

    摘要:本文基于小熊派开发板简单介绍了如何在LiteOS中移植传感器,从而实现对于传感器的相关控制. 1 hello world 相信大家无论在学习编程语言开始的第一个函数应该是HelloWorld,本 ...

  10. 论文分享丨Holistic Evaluation of Language Models

    摘要:该文为大模型评估方向的综述论文. 本文分享自华为云社区<[论文分享]<Holistic Evaluation of Language Models>>,作者:DevAI. ...