首页 > 编程语言 >Feign源码解析6:如何集成discoveryClient获取服务列表

Feign源码解析6:如何集成discoveryClient获取服务列表

时间:2024-01-20 18:44:08浏览次数:39  
标签:Feign discoveryClient 实例 spring bean 源码 DiscoveryClient ServiceInstanceListSuppl

背景

我们上一篇介绍了feign调用的整体流程,在@FeignClient没有写死url的情况下,就会生成一个支持客户端负载均衡的LoadBalancerClient。这个LoadBalancerClient可以根据服务名,去获取服务对应的实例列表,然后再用一些客户端负载均衡算法,从这堆实例列表中选择一个实例,再进行http调用即可。

image-20240114113200793

上图中,最核心的也就是2处。我们本次就从这里入手,去研究下,服务实例列表是如何获取到的,以及如何配置静态的服务实例地址。

服务实例列表相关bean初始化

在上图的2处开始执行前,有这么一行:

image-20240120154109681

这里就会去查找bean,类型是LoadBalancerLifecycle.class。去哪里查找呢,spring容器,但是是各个loadbalancer自己的spring容器。

image-20240120154327878

刚开始嘛,容器还没有,此时就会触发spring容器的创建和初始化。这个容器里有哪些bean呢?

主要的bean来源于LoadBalancerClientConfiguration这个配置类。里面包含了两个重要的bean,一个是loadbalancer,支持随机获取某个实例,但这个bean,可以从下面的代码看到,它的第一个构造参数,是去获取一个ServiceInstanceListSupplier类型的bean的provider,要靠这个provider提供服务实例列表。

所以,这个bean其实是依赖于ServiceInstanceListSupplier这种bean的。

image-20240120154830749

下面这个则是ServiceInstanceListSupplier类型,也就是实例列表提供者。

image-20240120154912336

ServiceInstanceListSupplierBuilder

ServiceInstanceListSupplier.builder():

static ServiceInstanceListSupplierBuilder builder() {
   return new ServiceInstanceListSupplierBuilder();
}

这个就是普通的建造者,没有什么特别。接下来,则是给builder设置DiscoveryClient,这个就是服务发现相关的client,比如eureka、nacos这些的客户端:

.withBlockingDiscoveryClient()

image-20240120155817648

这里我们发现一个一个箭头函数,这个箭头函数有一个入参,名字是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类型的对象返回。

image-20240120160309894

接下来,builder又设置了缓存:

ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching()

image-20240120160558992

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环节:

image-20240120162700048

可以看到,首先是执行了baseCreator,传入了spring上下文,此时就会触发之前看到的:

image-20240120162805147

CompositeDiscoveryClient

image-20240120162920373

我们上图中,获取到的DiscoveryClient类型的bean为CompositeDiscoveryClient。它就像它的名字一样,里面聚合了多个DiscoveryClient。

image-20240120163223173

这个bean的定义在哪里呢?这是靠自动装配引入的:

image-20240120163446228

image-20240120163401160

这个聚合类依赖的discoveryClient哪里来的呢?

首先是nacosDiscoveryClient:

image-20240120163733879

image-20240120163747810

再一个是SimpleDiscoveryClient类型:

image-20240120163911180

image-20240120163934666

多个DiscoveryClient的顺序

在CompositeDiscoveryClient中,是用list维护各个DiscoveryClient。

private final List<DiscoveryClient> discoveryClients;

谁先谁后,重要吗?看看下面的方法,是用来获取服务实例的:

image-20240120164341770

这里是先获取到则直接返回,说明还是很重要的。

各个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();
}

image-20240120165034092

你没有显示设置这个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;
	}

image-20240120165617210

它可以配置各个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会排在它前面,导致无法生效:

image-20240120171004638

所以,还得配上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

image-20240120171446534

DiscoveryClientServiceInstanceListSupplier

构造好了前面的CompositeDiscoveryClient,我们就会开始创建服务实例supplier。

image-20240120171806395

上图可以看到,这里有delegate.getInstances(serviceId),但后面又进行了封装,最终的类型是:

private final Flux<List<ServiceInstance>> serviceInstances;

这个Flux是反应式编程相关的api,不是很懂,但内部主要就是封装了一个数据源,等到需要获取服务实例的时候,就会真正调用到:

delegate.getInstances(serviceId)

届时,就会调用到:

image-20240120172923861

缓存包装

这个DiscoveryClientServiceInstanceListSupplier,后续又经过cache相关包装,最终的类型是:

CachingServiceInstanceListSupplier

image-20240120174647535

这个bean咱们就讲到这里。

reactorServiceInstanceLoadBalancer

接下来,开始看:

image-20240120173048151

第一个参数:

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处:

image-20240114113200793

首先就是获取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.

image-20240120183510301

然后调用loadBalancer.choose(request):image-20240120183623851

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):

image-20240120175141133

image-20240120175343055

然后对这个Flux<List<ServiceInstance>>类型的对象,执行next,把当前对象变成了MonoNext类型的对象,MonoNext的注释是:Emits a single item at most from the source.

image-20240120175522455

接下来是map操作,转成了一个MonoMap类型的对象:

image-20240120180301113

这里还不会实际触发上面的客户端负载均衡逻辑,此时只是封装成了MonoMap:

image-20240120180206884

把MonoMap丢给了如下的from函数,里面把MonoMap强转为了Mono类型:

image-20240120180407958

image-20240120180522105

接下来执行block操作,转为同步阻塞:

Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();

image-20240120180641337

这里我感觉就是,创建了一个实际的订阅者,且这个订阅者订阅了当前这个MonoMap,所以这个MonoMap就得真正开始干活了(之前只是把一堆操作给封装进去了,但没有实际做)。

此时,也会真正触发如下地方:

image-20240120181034847

这里完成后呢,就真正拿到了服务实例列表,此时,就会触发之前那个map函数:

image-20240120181221534

根据当前loadbalancer的算法(随机算法),进行多个服务实例中选一个的操作:

image-20240120181247633

image-20240120181343593

接着,我们终于拿到了一个实例了,可以进行后续调用了:

image-20240120181647071

总结

反应式编程,这个真是太难看懂了,实在是劝退。

今天是大寒,马上要更冷了,不过再坚持一阵,就能春暖花开了,兄弟们

参考

https://docs.spring.io/spring-cloud-commons/docs/3.1.8/reference/html/#zone-based-load-balancing

image-20240120182508336

https://mp.weixin.qq.com/s/aRpwCtgENCwubMF3idQQzQ

标签:Feign,discoveryClient,实例,spring,bean,源码,DiscoveryClient,ServiceInstanceListSuppl
From: https://www.cnblogs.com/grey-wolf/p/17976978

相关文章

  • 将小部分源码设计精髓带入到开发中来(工厂模式、适配器模式、抽象类、监听器)
    前言咋说呢,大学期间阅读过很多源码(Aop、Mybatis、Ioc、SpringMvc…),刚开始看这些源码的时候觉得云里雾里,一个没什么代码量的人突然去接触这种商业帝国级别的成品源码的时候,根本无从下手,这种感觉很难受,但是也庆幸自己熬过了那段难忘且充实的日子,随着自己代码量的慢慢增多,也开始慢慢......
  • 直播app系统源码,通过延迟加载非关键资源实现首屏优化
    直播app系统源码,通过延迟加载非关键资源实现首屏优化将非关键资源(如广告、推荐内容等)的加载延迟到首屏渲染完成之后,以提高首屏展示速度。<!DOCTYPEhtml><html><head><title>延迟加载示例</title></head><body><h1>首屏内容</h1><!--非关键资源--><d......
  • 视频直播app源码,利用缓存实现连续登录失败后的时间等待
    实现步骤:1、用户在视频直播app源码中发起登录请求2、后台验证是否失败次数过多,账户没有锁定的话就进入下面的步骤;否则直接返回3、验证用户的账号+密码3.1验证成功:删除缓存3.2验证失败:统计最近10分钟时间窗口内的失败次数,如果达到5次则设置锁定缓存,返回图解实......
  • MetaGPT day02: MetaGPT Role源码分析
    MetaGPT源码分析思维导图MetaGPT版本为v0.4.0,如下是frommetagpt.rolesimportRole,Role类执行Role.run时的思维导图:概述其中最重要的部分是_react,里面包含了一个循环,在循环中交替执行_think和_act,也就是让llm先思考再行动。_think中决定了llm下一个执行的动作是什么,这个动作......
  • TCP三次握手源码分析(服务端接收ACK&TCP连接建立完成)
    内核版本:Linux3.10内核源码地址:https://elixir.bootlin.com/linux/v3.10/source(包含各个版本内核源码,且网页可全局搜索函数)《TCP三次握手源码分析(客户端发送SYN)》《TCP三次握手源码分析(服务端接收SYN以及发送SYN+ACK)》《TCP三次握手源码分析(客户端接收SYN+ACK以及发送ACK......
  • 《Java并发实现原理:JDK源码剖析》PDF
    《Java并发实现原理:JDK源码剖析》全面而系统地剖析了JavaConcurrent包中的每一个部分,对并发的实现原理进行了深刻的探讨。全书分为8章,第1章从最基础的多线程知识讲起,理清多线程中容易误解的知识点,探究背后的原理,包括内存重排序、happen-before、内存屏障等;第2~8章,从简单到复杂,逐......
  • 《Java并发实现原理:JDK源码剖析》PDF
    《Java并发实现原理:JDK源码剖析》全面而系统地剖析了JavaConcurrent包中的每一个部分,对并发的实现原理进行了深刻的探讨。全书分为8章,第1章从最基础的多线程知识讲起,理清多线程中容易误解的知识点,探究背后的原理,包括内存重排序、happen-before、内存屏障等;第2~8章,从简单到复杂,逐......
  • A021 《斗图大赛》编程 源码
    一、课程介绍本节课将学习新的while循环,并结合布尔值True实现无限循环,最终实现一个动态表情包的效果。二、重难点解析布尔值在编程中,True是真,False是假。“真,假”,也是“对,错”的意思,它们是由英国著名数学家和逻辑学家乔治布尔提出的。所以,True和False也叫做布尔值,用于表示......
  • SpringBoot使用Feign进行服务间通信
    一、前言在分布式系统中,服务间通信是非常常见的情况。Feign是一个开源的JavaHTTP客户端,可以帮助我们在SpringBoot应用中快速构建和使用HTTP客户端,方便实现服务间的通信。与其他HTTP客户端相比,Feign具有简化HTTPAPI定义、支持多种HTTP请求方法、支持请求和响应的压缩、支持请求和......
  • 纯网页语音视频聊天和桌面分享(附源码,PC版+手机版)
    在网页里实现文字聊天是比较容易的,但若要实现视频聊天,就比较麻烦了。本文将实现一个纯网页版的视频聊天和桌面分享的Demo,可直接在浏览器中运行,不需要安装任何插件。一.主要功能及支持平台1.本Demo的主要功能有(1)一对一语音视频聊天。(2)远程桌面观看。(3)当客户端掉线时,会......