Feign源碼解析6:如何集成discoveryClient獲取服務列表

背景

我們上一篇介紹了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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章