亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解SpringCloud LoadBalancer 新一代負載均衡器

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

前言

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

java.net.ConnectException: Connection refused: connect

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

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

既然由于 Eureka 本身的設計導致會存在服務實例信息延遲更新,那么我們嘗試去修改幾個參數(shù)來降低延遲

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

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

LoadBalancer 如何獲取實例信息

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

通過 debug OpenFeign 調(diào)用的源碼發(fā)現(xiàn)它是從 DiscoveryClientServiceInstanceListSupplier的構造方法獲取實例信息集合 List<ServiceInstance> 的,內(nèi)部調(diào)用到 CachingServiceInstanceListSupplier 構造方法,重點看 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());
}

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

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

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

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

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

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

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

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

LoadBalancer 的兩種負載均衡策略

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

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);
}

這兩種策略都比較簡單,沒什么好說的。

輪詢策略存在的問題

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

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);
}

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

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

因為重試的時候是在 OpenFeign 內(nèi)部重新發(fā)起了一次 HTTP 請求,所以 traceId 并沒有變,我們可以先從 MDC 上下文獲取 traceId,再從緩存中獲取 traceId 對應的值,如果沒有就隨機生成一個數(shù)字然后和 RoundRobinLoadBalancer 一樣自增求余,如果緩存中已經(jīng)有了就直接自增求余,這樣就一定能重試到不同的實例。

這里我們緩存組件還是使用 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));
}

這個方法是從哈希哥那里學到的,他的主頁 juejin.cn/user/501033… 。

完了之后聲明我們自己的負載均衡器的 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);
    }
}

之后在主啟動類上使用 @LoadBalancerClient 指定我們自定義的負載均衡器

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

設置 LoadBalancer Zone

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

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 新一代負載均衡器的詳細內(nèi)容,更多關于SpringCloud LoadBalancer負載均衡器的資料請關注腳本之家其它相關文章!

相關文章

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

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

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

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

    這篇文章主要介紹了從JVM角度對MyEclipse進行調(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異常,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-07-07
  • Java中replace的用法實例講解

    Java中replace的用法實例講解

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

    JAVA Comparator 和 Comparable接口使用方法

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

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

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

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

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

    Java網(wǎng)絡通信中ServerSocket的設計優(yōu)化方案

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

    Netty?拆包沾包問題解決方案詳解

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

    Java從List中刪除元素的幾種方式小結

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

最新評論