欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解SpringCloud LoadBalancer 新一代負(fù)載均衡器

 更新時(shí)間:2023年01月16日 09:54:03   作者:暮色妖嬈丶  
這篇文章主要為大家介紹了SpringCloud LoadBalancer新一代負(fù)載均衡器詳解使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

工作中使用 OpenFeign 進(jìn)行跨服務(wù)調(diào)用,最近發(fā)現(xiàn)線上經(jīng)常會(huì)遇到請(qǐng)求失敗。

java.net.ConnectException: Connection refused: connect

通過(guò)排查我們發(fā)現(xiàn)不是接口超時(shí),而是有時(shí)候會(huì)請(qǐng)求到已經(jīng)下線的服務(wù)導(dǎo)致報(bào)錯(cuò)。這多發(fā)生在服務(wù)提供者系統(tǒng)部署的時(shí)候,因?yàn)橄到y(tǒng)部署的時(shí)候會(huì)調(diào)用 Spring 容器 的 shutdown() 方法, Eureka Server 那里能夠及時(shí)的剔除下線服務(wù),但是我們上一篇文章中已經(jīng)知道 readOnlyCacheMapreadWriteCacheMap 同步間隔是 30S,Client 端拉取實(shí)例信息的間隔也是 30S,這就導(dǎo)致 Eureka Client 端存儲(chǔ)的實(shí)例信息數(shù)據(jù)在一個(gè)臨界時(shí)間范圍內(nèi)都是臟數(shù)據(jù)。

調(diào)整 Eureka 參數(shù)

既然由于 Eureka 本身的設(shè)計(jì)導(dǎo)致會(huì)存在服務(wù)實(shí)例信息延遲更新,那么我們嘗試去修改幾個(gè)參數(shù)來(lái)降低延遲

  • Client 端設(shè)置服務(wù)拉取間隔3S, eureka.client.registry-fetch-interval-seconds = 3
  • Server 端設(shè)置讀寫(xiě)緩存同步間隔 3S,eureka.server.response-cache-update-interval-ms=3000

這樣設(shè)置之后經(jīng)過(guò)一段時(shí)間的觀察發(fā)現(xiàn)情況有所改善,但還是存在這個(gè)問(wèn)題,而且并沒(méi)有改善多少。

LoadBalancer 如何獲取實(shí)例信息

EurekaOpenFeign 的文章中都有提到,OpenFeign 進(jìn)行遠(yuǎn)程調(diào)用的時(shí)候會(huì)通過(guò)負(fù)載均衡器選取一個(gè)實(shí)例發(fā)起 Http 請(qǐng)求。我們 SpringCloud 版本是 2020,已經(jīng)移除了 ribbon,使用的是 LoadBalancer

通過(guò) debug OpenFeign 調(diào)用的源碼發(fā)現(xiàn)它是從 DiscoveryClientServiceInstanceListSupplier的構(gòu)造方法獲取實(shí)例信息集合 List<ServiceInstance> 的,內(nèi)部調(diào)用到 CachingServiceInstanceListSupplier 構(gòu)造方法,重點(diǎn)看 CacheFlux.lookup()

public CachingServiceInstanceListSupplier(ServiceInstanceListSupplier delegate, CacheManager cacheManager) {
   super(delegate);
   this.serviceInstances = CacheFlux.lookup(key -> {
      // TODO: configurable cache name
      Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);
      if (cache == null) {
         if (log.isErrorEnabled()) {
            log.error("Unable to find cache: " + SERVICE_INSTANCE_CACHE_NAME);
         }
         return Mono.empty();
      }
      List<ServiceInstance> list = cache.get(key, List.class);
      if (list == null || list.isEmpty()) {
         return Mono.empty();
      }
      return Flux.just(list).materialize().collectList();
   }, delegate.getServiceId()).onCacheMissResume(delegate.get().take(1))
         .andWriteWith((key, signals) -> Flux.fromIterable(signals).dematerialize().doOnNext(instances -> {
            Cache cache = cacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);
            if (cache == null) {
               if (log.isErrorEnabled()) {
                  log.error("Unable to find cache for writing: " + SERVICE_INSTANCE_CACHE_NAME);
               }
            }
            else {
               cache.put(key, instances);
            }
         }).then());
}

這里先去查緩存,緩存有就直接返回,緩存沒(méi)有就去 CompositeDiscoveryClient.getInstances() 查詢。查詢完畢之后會(huì)回調(diào)到 CacheFlux.lookup(param,param2) 第二個(gè)參數(shù)的代碼塊,將結(jié)果放進(jìn)緩存。

@Override
public List<ServiceInstance> getInstances(String serviceId) {
   if (this.discoveryClients != null) {
      for (DiscoveryClient discoveryClient : this.discoveryClients) {
         List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
         if (instances != null && !instances.isEmpty()) {
            return instances;
         }
      }
   }
   return Collections.emptyList();
}

重點(diǎn)看這個(gè)方法,由于我們使用的是 Eureka 作為注冊(cè)中心。所以這里會(huì)調(diào)用 EurekaDiscoveryClientgetInstances(), 最終我們發(fā)現(xiàn)底層其實(shí)就是從 DiscoveryClient.localRegionApps 獲取的服務(wù)實(shí)例信息。

現(xiàn)在我們清楚了,OpenFeign 調(diào)用時(shí),負(fù)載均衡策略還不是從 DiscoveryClient.localRegionApps 直接拿的實(shí)例信息,是自己緩存了一份。這樣一來(lái),不僅要計(jì)算 Eureka 本身的延遲,還要算上緩存時(shí)間。

SpringCloud 中有很多內(nèi)存緩存的實(shí)現(xiàn),這里我們選擇的是 Caffine

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.0.5</version>
</dependency>

引入依賴即可自動(dòng)配置,從 LoadBalancerCacheProperties 中我們能夠發(fā)現(xiàn)默認(rèn)的緩存時(shí)間是 35S,所以要解決我們的問(wèn)題還需要降低緩存時(shí)間,也可以直接不使用內(nèi)存緩存,每次都從 EurekaClient 拉取過(guò)來(lái)的實(shí)例信息讀取即可。

通過(guò)上面的分析我們可以發(fā)現(xiàn)使用 OpenFeign 內(nèi)部調(diào)用是無(wú)法根治這個(gè)問(wèn)題的,因?yàn)?Eureka 的延遲是無(wú)法根治的,只能說(shuō)在維持機(jī)器性能等各方面的前提下盡可能的縮短數(shù)據(jù)同步定時(shí)任務(wù)的時(shí)間間隔。所以我們可以換個(gè)角度,讓調(diào)用失敗的請(qǐng)求進(jìn)行重試。

LoadBalancer 的兩種負(fù)載均衡策略

通過(guò)源碼調(diào)試,發(fā)現(xiàn)它有兩種負(fù)載均衡策略 RoundRobinLoadBalancer、RandomLoadBalancer,輪詢和隨機(jī),默認(rèn)的策略是輪詢

LoadBalancerClientConfiguration

@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
      LoadBalancerClientFactory loadBalancerClientFactory) {
   String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
   return new RoundRobinLoadBalancer(
         loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}

這兩種策略都比較簡(jiǎn)單,沒(méi)什么好說(shuō)的。

輪詢策略存在的問(wèn)題

我們可以觀察下輪詢策略的實(shí)現(xiàn),它有一個(gè)原子類型的成員變量,用來(lái)記錄下一次請(qǐng)求要落到哪一個(gè)實(shí)例

final AtomicInteger position;

核心邏輯

private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
   if (instances.isEmpty()) {
      if (log.isWarnEnabled()) {
         log.warn("No servers available for service: " + serviceId);
      }
      return new EmptyResponse();
   }
   // TODO: enforce order?
   int pos = Math.abs(this.position.incrementAndGet());
   ServiceInstance instance = instances.get(pos % instances.size());
   return new DefaultResponse(instance);
}

可以看到實(shí)現(xiàn)邏輯很簡(jiǎn)單,用 position 自增,然后實(shí)例數(shù)量進(jìn)行求余,達(dá)到輪詢的效果。乍一看好像沒(méi)問(wèn)題,但是它存在這樣一種情況?,F(xiàn)在我們有兩個(gè)實(shí)例 192.168.1.121、192.168.1.122,這時(shí)候兩個(gè)請(qǐng)求 A、B 過(guò)來(lái),A 請(qǐng)求了 121 的,B 請(qǐng)求了 122 的,然后 A 請(qǐng)求失敗了觸發(fā)重試,由于輪詢機(jī)制 A 重試的實(shí)例又回到了 121 ,這樣就有問(wèn)題了,因?yàn)檫€是失敗,我們要讓重試的請(qǐng)求一定能重試到其他的服務(wù)實(shí)例。

使用 TraceId 實(shí)現(xiàn)自定義負(fù)載均衡策略

因?yàn)橹卦嚨臅r(shí)候是在 OpenFeign 內(nèi)部重新發(fā)起了一次 HTTP 請(qǐng)求,所以 traceId 并沒(méi)有變,我們可以先從 MDC 上下文獲取 traceId,再?gòu)木彺嬷蝎@取 traceId 對(duì)應(yīng)的值,如果沒(méi)有就隨機(jī)生成一個(gè)數(shù)字然后和 RoundRobinLoadBalancer 一樣自增求余,如果緩存中已經(jīng)有了就直接自增求余,這樣就一定能重試到不同的實(shí)例。

這里我們緩存組件還是使用 Caffeine

private final LoadingCache<String, AtomicInteger> positionCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES)
      .build(k -> new AtomicInteger(ThreadLocalRandom.current().nextInt(0, 1000)));
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances) {
   if (serviceInstances.isEmpty()) {
      log.warn("No servers available for service: " + serviceId);
      return new EmptyResponse();
   }
   String traceId = MDC.get("traceId");
   if (traceId == null) {
      traceId = UUID.randomUUID().toString();
   }
   AtomicInteger seed = positionCache.get(traceId);
   int s = seed.getAndIncrement();
   int pos = s % serviceInstances.size();
   return new DefaultResponse(serviceInstances.stream()
         .sorted(Comparator.comparing(ServiceInstance::getInstanceId))
         .collect(Collectors.toList()).get(pos));
}

這個(gè)方法是從哈希哥那里學(xué)到的,他的主頁(yè) juejin.cn/user/501033…

完了之后聲明我們自己的負(fù)載均衡器的 Bean

public class FeignLoadBalancerConfiguration {
    @Bean
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSuppliers, Environment environment) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RoundRobinRetryDifferentInstanceLoadBalancer(serviceInstanceListSuppliers,name);
    }
}

之后在主啟動(dòng)類上使用 @LoadBalancerClient 指定我們自定義的負(fù)載均衡器

@LoadBalancerClient(name = "feign-test-product", configuration = FeignLoadBalancerConfiguration.class)

設(shè)置 LoadBalancer Zone

還記得之前 Eureka 我們?yōu)榱私鉀Q本機(jī)調(diào)用的時(shí)候會(huì)通過(guò)負(fù)載均衡調(diào)用到開(kāi)發(fā)環(huán)境的機(jī)器設(shè)置了 zone,SpringCloud LoadBalancer 也提供了這個(gè)配置,并且從源碼中我們可以發(fā)現(xiàn),最終會(huì)以 LoadBalancer 設(shè)置的為準(zhǔn),如果沒(méi)有為它設(shè)置,那么會(huì)使用 Eureka 中的 zone 配置,如果設(shè)置了就會(huì)覆蓋 Eurekazone 設(shè)置

EurekaLoadBalancerClientConfiguration.postprocess()

@PostConstruct
public void postprocess() {
   if (!StringUtils.isEmpty(zoneConfig.getZone())) {
      return;
   }
   String zone = getZoneFromEureka();
   if (!StringUtils.isEmpty(zone)) {
      if (LOG.isDebugEnabled()) {
         LOG.debug("Setting the value of '" + LOADBALANCER_ZONE + "' to " + zone);
      }
      zoneConfig.setZone(zone);
   }
}

以上就是詳解SpringCloud LoadBalancer 新一代負(fù)載均衡器的詳細(xì)內(nèi)容,更多關(guān)于SpringCloud LoadBalancer負(fù)載均衡器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • MybatisPlus多表連接查詢的具體實(shí)現(xiàn)

    MybatisPlus多表連接查詢的具體實(shí)現(xiàn)

    MyBatis Plus是一款針對(duì)MyBatis框架的增強(qiáng)工具, 它提供了很多方便的方法來(lái)實(shí)現(xiàn)多表聯(lián)查,本文主要介紹了MybatisPlus多表連接查詢的具體實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-10-10
  • JVM角度調(diào)試優(yōu)化MyEclipse

    JVM角度調(diào)試優(yōu)化MyEclipse

    這篇文章主要介紹了從JVM角度對(duì)MyEclipse進(jìn)行調(diào)試優(yōu)化,為大家分析調(diào)試優(yōu)化MyEclipse的步驟,感興趣的小伙伴們可以參考一下
    2016-05-05
  • Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.Type異常

    Caused by: java.lang.ClassNotFoundException: org.objectweb.a

    這篇文章主要介紹了Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.Type異常,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Java中replace的用法實(shí)例講解

    Java中replace的用法實(shí)例講解

    這篇文章主要給大家介紹了關(guān)于Java中replace用法的相關(guān)資料,Java中的replace方法是用于字符串替換的方法,它可以接受兩個(gè)參數(shù),第一個(gè)參數(shù)是需要被替換的字符串,第二個(gè)參數(shù)是替換后的字符串,需要的朋友可以參考下
    2024-04-04
  • JAVA Comparator 和 Comparable接口使用方法

    JAVA Comparator 和 Comparable接口使用方法

    本文介紹了Java中Comparable和Comparator接口的使用,包括它們的定義、方法和應(yīng)用場(chǎng)景,Comparable用于定義類的自然排序規(guī)則,而Comparator提供了一種靈活的方式來(lái)定義對(duì)象之間的排序規(guī)則,無(wú)需修改類本身,感興趣的朋友一起看看吧
    2025-03-03
  • SpringMVC下實(shí)現(xiàn)Excel文件上傳下載

    SpringMVC下實(shí)現(xiàn)Excel文件上傳下載

    這篇文章主要為大家詳細(xì)介紹了SpringMVC下實(shí)現(xiàn)Excel文件上傳下載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • 詳解基于java的Socket聊天程序——客戶端(附demo)

    詳解基于java的Socket聊天程序——客戶端(附demo)

    這篇文章主要介紹了詳解基于java的Socket聊天程序——客戶端(附demo),客戶端設(shè)計(jì)主要分成兩個(gè)部分,分別是socket通訊模塊設(shè)計(jì)和UI相關(guān)設(shè)計(jì)。有興趣的可以了解一下。
    2016-12-12
  • Java網(wǎng)絡(luò)通信中ServerSocket的設(shè)計(jì)優(yōu)化方案

    Java網(wǎng)絡(luò)通信中ServerSocket的設(shè)計(jì)優(yōu)化方案

    今天小編就為大家分享一篇關(guān)于Java網(wǎng)絡(luò)通信中ServerSocket的設(shè)計(jì)優(yōu)化方案,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-04-04
  • Netty?拆包沾包問(wèn)題解決方案詳解

    Netty?拆包沾包問(wèn)題解決方案詳解

    這篇文章主要為大家介紹了Netty?拆包沾包問(wèn)題解決方案示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • Java從List中刪除元素的幾種方式小結(jié)

    Java從List中刪除元素的幾種方式小結(jié)

    在Java中,List 接口提供了一個(gè) remove(Object o) 方法來(lái)移除列表中與給定對(duì)象相等的第一個(gè)元素,然而,直接使用這個(gè)方法來(lái)刪除列表中的元素有時(shí)并不是最優(yōu)的選擇,主要原因包括效率和同步性問(wèn)題,本文介紹了Java從List中刪除元素的幾種方式,需要的朋友可以參考下
    2024-08-08

最新評(píng)論