背景

我们上一篇介绍了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. 2024年 为什么不建议新人学习ABAP

    引言 每个应届生都希望自己有良好的职业发展,当他们发现前路难通时,便会寻找更好的出路. "转码"曾经是个很火热的话题.在互联网行业高速发展的年代,转行学代码,入职大厂,升职加薪,是 ...

  2. 三个月我迁移了100PB数据

    2023年马上结束,这一年做了不少事情,有一项我可以吹好几年,忍不住和大家分享一下啊. 需求 去年底收到一项需求,要求2个月内从某云存储迁移100PB数据到微软云存储,包含几百亿个文件.当时听到这个数 ...

  3. Pikachu漏洞靶场 PHP反序列化

    PHP反序列化 查看源码,以下为关键代码: class S{ var $test = "pikachu"; function __construct(){ echo $this-& ...

  4. JavaFx之SceneBuilder添加其他依赖库(十六)

    JavaFx之SceneBuilder添加其他依赖库(十六) Could not open 'xxxxx.jar' Open operation has failed. Make sure that ...

  5. Ubuntu 之 7zip使用

    1.安装 sudo apt-get install p7zip 2.压缩 7zr a xxx foldername 3.解压缩 7zr x xxx.7z 4.zip命令压缩文件夹 zip -qr xx ...

  6. 计算机网络分层结构--OSI模型、TCP/IP 模型、五层模型

    计算机网络分层结构 OSI参考模型与TCP/IP参考模型 五层参考模型

  7. osgEarth使用笔记2——推荐两个底图数据

    目录 1. 概述 2. 详论 2.1. Blue Marble 2.2. Bright Earth eAtlas Basemap 3. 分享 1. 概述 可以通过osgEarth自带的world.ti ...

  8. 去哪儿网 (Qunar) DevOps 实践分享

    这是 2017 年王晓翔在 msup 全球软件案例研究峰会上的分享,重点分享了提高工程效率过程中存在的问题.取得的成果和要做的事情.内容详实,具有可操作性.我有幸看到了,所以在征得晓翔的同意下重新截图 ...

  9. 容器中域名解析流程以及不同dnsPolicy对域名解析影响

    本文分享自华为云社区<容器中域名解析流程以及不同dnsPolicy对域名解析影响>,作者:可以交个朋友 . 一.coreDNS背景 部署在kubernetes集群中的容器业务通过coreD ...

  10. 加快脑动脉瘤检测,AI来了

    摘要:华为云EI创新孵化Lab联合华中科技大学电信学院.华中科技大学同济医学院附属协和医院放射科在放射学领域的国际顶级期刊Radiology(<放射学>)上共同发表了最新研究成果. 日前, ...