Spring Cloud LoadBalancer 詳解

????????在分布式系統快速發展的當下,服務間的調用日益頻繁且復雜。如何合理分配請求流量,避免單個服務節點過載,保障系統的穩定性與高效性,成為關鍵問題。負載均衡技術便是解決這一問題的重要手段。Spring Cloud LoadBalancer 作為 Spring Cloud 官方推出的負載均衡器,在微服務架構中發揮著至關重要的作用。本文將對其進行詳細解析。

一、Spring Cloud LoadBalancer 基本概念

????????Spring Cloud LoadBalancer 是 Spring Cloud 生態體系中的一款負載均衡器,它是 Spring Cloud 官方為了替代 Netflix Ribbon 而推出的。其主要功能是在微服務架構中,將客戶端的請求均勻地分發到多個服務實例上,從而實現服務的負載均衡,提高系統的可用性和可靠性。

二、核心原理

(一)服務發現機制

????????Spring Cloud LoadBalancer 依托于 Spring Cloud 的服務注冊與發現組件(如 Eureka、Nacos、Consul 等)來獲取服務實例列表。當服務啟動時,會向注冊中心注冊自己的信息(包括服務名稱、IP 地址、端口等)。Spring Cloud LoadBalancer 會定期從注冊中心拉取服務實例列表,并將其緩存到本地。同時,它也會監聽注冊中心的服務實例變化事件,當有服務實例新增、下線或發生故障時,能及時更新本地的服務實例列表,以保證獲取到的服務實例是可用的。

(二)負載均衡策略

????????Spring Cloud LoadBalancer 提供了多種負載均衡策略,用于從服務實例列表中選擇一個合適的服務實例來處理當前請求。常見的負載均衡策略如下:

  1. 輪詢(Round Robin):按照服務實例的順序,依次將請求分發到每個服務實例上。這種策略簡單直觀,適用于所有服務實例性能相近的場景。例如,有 3 個服務實例 A、B、C,請求會按照 A→B→C→A→B→C 的順序進行分發。
  2. 隨機(Random):隨機從服務實例列表中選擇一個服務實例來處理請求。該策略適用于服務實例性能差異不大,且希望請求分布相對均勻的場景。
  3. 權重(Weighted):為每個服務實例分配一個權重,權重越高的服務實例被選中的概率越大。這種策略可以根據服務實例的性能來分配權重,性能好的實例分配較高權重,適用于服務實例性能存在差異的場景。
  4. 最少連接數(Least Connections):選擇當前連接數最少的服務實例來處理請求。該策略能夠動態地根據服務實例的負載情況進行請求分發,適用于長連接場景,可有效避免服務實例過載。

三、使用方式

(一)引入依賴

????????在 Spring Boot 項目的 pom.xml 文件中引入 Spring Cloud LoadBalancer 的相關依賴。如果使用的是 Spring Cloud Alibaba 生態,可引入如下依賴:

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency>

同時,還需要引入對應的服務注冊與發現組件依賴,如 Nacos 的依賴:

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>

(二)進行配置

????????在 application.yml 或 application.properties 文件中進行相關配置。主要包括服務注冊中心的地址等配置,以 Nacos 為例:

spring:cloud:nacos:discovery:server-addr: 127.0.0.1:8848  # Nacos服務注冊中心地址application:name: service-consumer  # 當前服務名稱

????????對于 Spring Cloud LoadBalancer 本身,也可以進行一些自定義配置,如負載均衡策略的配置。可以通過在配置類中定義對應的 Bean 來指定負載均衡策略。

(三)代碼中使用

在代碼中,可以使用@LoadBalanced注解修飾 RestTemplate,使其具備負載均衡的能力。示例如下:

@Configuration
public class RestTemplateConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}
}

然后在服務調用的地方,使用 RestTemplate 根據服務名稱調用對應的服務:

@Service
public class ConsumerService {@Autowiredprivate RestTemplate restTemplate;public String callProvider() {// service-provider為服務提供者的服務名稱return restTemplate.getForObject("http://service-provider/hello", String.class);}
}

四、幾種負載均衡方式的代碼舉例

(一)輪詢策略(Round Robin)

輪詢策略是 Spring Cloud LoadBalancer 的默認策略之一,可通過如下配置類指定:

@Configuration
public class RoundRobinLoadBalancerConfig {// 配置輪詢負載均衡器@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {// 獲取服務名稱String serviceName = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);// 創建并返回輪詢負載均衡器實例return new RoundRobinLoadBalancer(// 獲取服務實例列表供應商loadBalancerClientFactory.getLazyProvider(serviceName, ServiceInstanceListSupplier.class),serviceName);}
}

????????該配置會使請求按照服務實例的注冊順序依次分發,例如有實例 A、B、C,請求會按 A→B→C→A 的順序循環分配。

(二)隨機策略(Random)

隨機策略通過隨機選擇服務實例處理請求,配置代碼如下:

@Configuration
public class RandomLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String serviceName = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);// 使用隨機負載均衡器return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(serviceName, ServiceInstanceListSupplier.class),serviceName);}
}

????????隨機策略的核心邏輯是在服務實例列表中隨機生成索引并選擇對應實例,適用于實例性能相近且需分散請求的場景。

(三)權重策略(Weighted)

Spring Cloud LoadBalancer 默認未提供權重策略,需自定義實現。可通過服務實例元數據配置權重,再實現權重選擇邏輯:

? ? ? ? 1、服務注冊時在元數據中添加權重(以 Nacos 為例,在服務提供者的 application.yml 中配置):

spring:cloud:nacos:discovery:metadata:weight: 3  # 權重值,可根據實例性能設置(如1-10)

????????2、自定義權重負載均衡器:

public class WeightedLoadBalancer extends AbstractLoadBalancer<ServiceInstance> {private final String serviceId;private final ServiceInstanceListSupplier supplier;public WeightedLoadBalancer(ServiceInstanceListSupplier supplier, String serviceId) {super(supplier);this.serviceId = serviceId;this.supplier = supplier;}@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {// 獲取服務實例列表return supplier.get().next().map(this::getWeightedInstance);}// 根據權重選擇實例private Response<ServiceInstance> getWeightedInstance(List<ServiceInstance> instances) {if (instances.isEmpty()) {return new EmptyResponse();}// 計算總權重(此處使用了元數據中的weight)int totalWeight = 0;for (ServiceInstance instance : instances) {totalWeight += getWeight(instance); // 循環調用getWeight獲取每個實例的權重}// 隨機生成權重范圍內的數值int randomWeight = new Random().nextInt(totalWeight) + 1;// 根據權重選擇實例(再次使用元數據中的weight)int currentWeight = 0;for (ServiceInstance instance : instances) {currentWeight += getWeight(instance); // 累加每個實例的權重if (currentWeight >= randomWeight) {return new DefaultResponse(instance);}}// 兜底返回第一個實例return new DefaultResponse(instances.get(0));}// 從元數據中獲取權重(核心:讀取Nacos配置的weight)private int getWeight(ServiceInstance instance) {// 從服務實例的元數據中獲取配置的weightString weightStr = instance.getMetadata().get("weight");// 如果未配置則默認權重為1,否則轉換為整數return weightStr != null ? Integer.parseInt(weightStr) : 1;}
}

????????3、配置自定義權重負載均衡器:

@Configuration
public class WeightedLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String serviceName = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);ServiceInstanceListSupplier supplier = loadBalancerClientFactory.getLazyProvider(serviceName, ServiceInstanceListSupplier.class);return new WeightedLoadBalancer(supplier, serviceName);}
}

權重使用說明

  • 元數據中配置的weight會被getWeight方法讀取(instance.getMetadata().get("weight"))
  • 計算總權重時,循環調用getWeight累加所有實例的權重值
  • 選擇實例時,通過累加權重與隨機數對比,權重越高的實例被選中的概率越大

例如:若有兩個實例,A 配置weight:3、B 配置weight:1,總權重為 4,A 被選中的概率是 3/4,B 是 1/4

(四)最少連接數策略(Least Connections)

最少連接數策略需跟蹤實例的連接數,選擇連接數最少的實例,實現如下:

????????1、自定義連接數跟蹤工具類:

// 連接數跟蹤器(單例)
public class ConnectionCounter {private static final ConnectionCounter INSTANCE = new ConnectionCounter();// 存儲實例ID與連接數的映射private final Map<String, AtomicInteger> connectionCounts = new ConcurrentHashMap<>();private ConnectionCounter() {}public static ConnectionCounter getInstance() {return INSTANCE;}// 增加實例連接數public void increment(ServiceInstance instance) {String instanceId = getInstanceId(instance);connectionCounts.computeIfAbsent(instanceId, k -> new AtomicInteger(0)).incrementAndGet();}// 減少實例連接數public void decrement(ServiceInstance instance) {String instanceId = getInstanceId(instance);AtomicInteger count = connectionCounts.get(instanceId);if (count != null) {count.decrementAndGet();}}// 獲取實例連接數public int getCount(ServiceInstance instance) {return connectionCounts.getOrDefault(getInstanceId(instance), new AtomicInteger(0)).get();}// 生成實例唯一標識(IP:端口)private String getInstanceId(ServiceInstance instance) {return instance.getHost() + ":" + instance.getPort();}
}

????????2、自定義最少連接數負載均衡器:

public class LeastConnectionsLoadBalancer extends AbstractLoadBalancer<ServiceInstance> {private final String serviceId;private final ServiceInstanceListSupplier supplier;public LeastConnectionsLoadBalancer(ServiceInstanceListSupplier supplier, String serviceId) {super(supplier);this.serviceId = serviceId;this.supplier = supplier;}@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {return supplier.get().next().map(this::getLeastConnectionInstance);}// 選擇連接數最少的實例private Response<ServiceInstance> getLeastConnectionInstance(List<ServiceInstance> instances) {if (instances.isEmpty()) {return new EmptyResponse();}ServiceInstance leastInstance = instances.get(0);int minCount = ConnectionCounter.getInstance().getCount(leastInstance);// 遍歷找到連接數最少的實例for (ServiceInstance instance : instances) {int count = ConnectionCounter.getInstance().getCount(instance);if (count < minCount) {minCount = count;leastInstance = instance;}}// 增加選中實例的連接數ConnectionCounter.getInstance().increment(leastInstance);return new DefaultResponse(leastInstance);}
}

????????3、使用時需在請求完成后減少連接數(以 AOP 為例):

@Aspect
@Component
public class ConnectionCountAspect {// 攔截服務調用方法,在完成后減少連接數@AfterReturning("execution(* com.example.consumer.service.ConsumerService.callProvider(..)) && args(..)")public void afterCall() {// 實際應用中需通過上下文獲取當前選中的實例// 此處簡化處理,實際需結合負載均衡器的選擇結果}
}

????????4、配置最少連接數負載均衡器:

@Configuration
public class LeastConnectionsConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String serviceName = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);ServiceInstanceListSupplier supplier = loadBalancerClientFactory.getLazyProvider(serviceName, ServiceInstanceListSupplier.class);return new LeastConnectionsLoadBalancer(supplier, serviceName);}
}

五、與 Ribbon 的對比

  1. 出身與支持:Spring Cloud LoadBalancer 是 Spring Cloud 官方推出的,與 Spring Cloud 生態的融合度更高,后續會得到持續的更新和支持。而 Ribbon 是 Netflix 開源的組件,Netflix 已經宣布停止對部分組件的維護,雖然 Ribbon 目前仍可使用,但長期來看,Spring Cloud LoadBalancer 是更好的替代選擇。
  2. 功能:兩者都能實現基本的負載均衡功能。不過 Spring Cloud LoadBalancer 在設計上更加簡潔,同時支持響應式編程,能更好地適配 Spring Cloud 的響應式生態。Ribbon 的功能相對豐富一些,提供了更多的負載均衡策略和配置選項,但也因此顯得較為復雜。
  3. 性能:在性能方面,Spring Cloud LoadBalancer 由于設計簡潔,在一些場景下表現可能更優。而 Ribbon 由于功能較多,可能會有一定的性能開銷。

六、總結與建議

(一)總結

????????Spring Cloud LoadBalancer 作為 Spring Cloud 官方的負載均衡器,具有與 Spring Cloud 生態融合度高、支持響應式編程、設計簡潔等優勢。它通過服務發現機制獲取服務實例列表,再結合各種負載均衡策略將請求分發到合適的服務實例,有效實現了服務的負載均衡。

????????它適用于大多數微服務架構場景,尤其是在采用 Spring Cloud 響應式生態(如使用 WebFlux)的項目中,能更好地發揮其優勢。

(二)建議

  1. 對于新的 Spring Cloud 項目,建議優先選擇 Spring Cloud LoadBalancer,以獲得更好的官方支持和生態適配。
  2. 在選擇負載均衡策略時,要根據實際的業務場景和服務實例的性能情況進行選擇。如果服務實例性能相近,輪詢或隨機策略即可;如果服務實例性能差異較大,可考慮權重策略;如果是長連接場景,最少連接數策略可能更合適。
  3. 在使用過程中,要合理配置服務注冊中心的相關參數,確保服務發現的及時性和準確性,從而保證負載均衡的效果。
  4. 自定義負載均衡策略時,需考慮線程安全(如使用 ConcurrentHashMap 存儲連接數)和性能開銷,避免因策略邏輯復雜導致服務響應延遲。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/89217.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/89217.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/89217.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux內核內存管理相關的配置參數

Linux內核內存管理相關的配置參數&#xff08;主要位于/proc/sys/vm/目錄下&#xff09;&#xff0c;用于調整內存分配、緩存管理、交換機制、OOM&#xff08;內存溢出&#xff09;策略等核心內存行為。以下是對每個參數的詳細解釋&#xff1a; admin_reserve_kbytes block_dum…

Web開發 01

先放一下自己寫的手敲的第一個網站代碼&#xff01;~雖然很簡單但還是有點成就感&#xff01;&#xff01;開心&#x1f60a;<!DOCTYPE html> <html><head><title>Title!</title><link rel "stylesheet"href "style.css"…

Redis 生產實戰 7×24:容量規劃、性能調優、故障演練與成本治理 40 條軍規

&#xff08;一&#xff09;寫在前面&#xff1a;為什么需要“軍規” Redis 在測試環境跑得飛快&#xff0c;一到線上就“莫名其妙”抖動&#xff1b;大促前擴容 3 倍&#xff0c;成本卻翻 5 倍&#xff1b;一次主從切換&#xff0c;緩存雪崩導致下游 DB 被打掛&#xff1b;開發…

【DOCKER】綜合項目 MonitorHub (監控中心)

文章目錄1、項目架構圖1.1 架構組件2、實際實施2.1 安裝docker2.2 編寫dockerfile文件2.2.1 Prometheus2.2.2 node_exporter2.2.3 nginxvts模塊2.2.4 nginx_exporeter 服務發現文件2.2.5 maridb dockerfile文件2.2.6 鏡像總數2.3 具體操作2.3.1 Prometheus組件2.3.2 nginx組件2…

Java List 集合詳解:從基礎到實戰,掌握 Java 列表操作全貌

作為一名 Java 開發工程師&#xff0c;你一定在項目中頻繁使用過 List 集合。它是 Java 集合框架中最常用、最靈活的數據結構之一。無論是從數據庫查詢出的數據&#xff0c;還是前端傳遞的參數列表&#xff0c;List 都是處理這些數據的首選結構。本文將帶你全面掌握&#xff1a…

SGMD辛幾何模態分解 直接替換Excel運行包含頻譜圖相關系數圖 Matlab語言!

SGMD辛幾何模態分解 直接替換Excel運行包含頻譜圖相關系數圖 Matlab語言算法近幾年剛提出&#xff0c;知網還沒幾個人用&#xff0c;你先用&#xff0c;你就是創新&#xff01;算法新穎小眾&#xff0c;用的人很少&#xff0c;包含分解圖、頻譜圖、相關系數圖&#xff0c;效果如…

Oracle數據泵詳解——讓數據遷移像“點外賣”一樣簡單?

?今天我想和大家聊一個數據庫領域的“萬能搬運工”——Oracle數據泵&#xff08;Data Pump&#xff09;?。相信很多人都有過這樣的經歷&#xff1a;業務要上線新系統&#xff0c;得把舊庫的數據搬到新環境&#xff1b;或者領導突然要一份3年前的歷史數據&#xff0c;可不能影…

Leetcode 03 java

爬樓梯算法現在只看明白動態規劃&#xff0c;也沒有很難喲&#xff01;&#xff01;題目70. 爬樓梯假設你正在爬樓梯。需要 n 階你才能到達樓頂。每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢&#xff1f;java題解class Solution {public int climbStairs(…

怎么刪除 wps 的右鍵菜單

打開 WPS 點擊 WPS Office 選項卡&#xff0c;點擊右側全局配置》配置和修復工具點擊高級功能定制下的都可以關閉和隱藏點擊確定就可以了。

C++:list

一&#xff0c;list的介紹1&#xff0c;list初步&#xff08;1&#xff09;list是 C 標準模板庫 (STL) 中的一個雙向鏈表容器。它允許在常數時間內進行任意位置的插入和刪除操作&#xff0c;但不支持隨機訪問。&#xff08;2&#xff09;list容器的底層數據結構為帶頭雙向循環鏈…

深入理解Collections.addAll方法

文章目錄深入理解Collections.addAll方法概述方法定義基本用法1. 向List添加元素2. 向Set添加元素3. 添加數組元素與傳統add方法的比較使用傳統add方法使用Collections.addAll性能考慮注意事項實際應用場景與Collection.addAll的區別最佳實踐總結深入理解Collections.addAll方法…

CISP-PTE 練習題(完整一套)

目錄 1、SQL注入 2、文件上傳 3、文件包含 4、代碼審計 5、命令執行 6、端口掃描 7、sql 寫 webshell 8、3389 遠程桌面利用 1、SQL注入 sqllabs-less-24 二次注入 2、文件上傳 沒有對文件后綴進行檢測&#xff0c;但是對文件類型有檢測&#xff0c;需要使用圖片頭繞…

Vue3入門-計算屬性+監聽器

&#x1f3e0;個人主頁&#xff1a;Yui_ &#x1f351;操作環境&#xff1a;vscode\node.js &#x1f680;所屬專欄&#xff1a;Vue3 文章目錄1. 計算屬性1.1 computed函數1.2 計算屬性VS普通函數1.3 計算屬性的完整寫法2. 監聽器3.總結1. 計算屬性 計算屬性&#xff08;compu…

Linux Swap區深度解析:為何禁用?何時需要?

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄前言一、Swap區&#xff1a;Linux的"內存救生圈"二、為什么要禁用Swap&#xff1f;性能的隱形殺手三、何時應該使用Swap&#xff1f;不可或缺的場景四、如…

用TensorFlow進行邏輯回歸(三)

邏輯回歸Logistic regression這個腳本展示如何用TensorFlow求解邏輯回歸。 ()ysigmoid(Axb)我們使用低出生重量數據,特別地:y 0 or 1 low birth weightx demographic and medical history dataimport matplotlib.pyplot as pltimport numpy as npimport tensorflow as tfimp…

mingw 編譯 assimp v6.0.2 解決編譯報錯

mingw 編譯 assimp v6.0.2 理論上看這個就能滿足&#xff1a;在Windows下使用CMakeMinGW64編譯Assimp庫 環境變量問題 i386 architecture of input file CMakeFiles\assimp.dir/objects.a(assimp.rc.obj)’ is incompatible with i386:x86-64 output collect2.exe: error: ld r…

Windows 11清理C盤方法大全:磁盤清理/禁用休眠/系統還原點/優化大師使用教程

Windows 11清理C盤方法1. 使用磁盤清理工具步驟&#xff1a;按 Win S 搜索“磁盤清理”&#xff0c;打開工具。選擇C盤&#xff0c;點擊“確定”。勾選需要清理的文件類型&#xff08;如臨時文件、系統錯誤內存轉儲等&#xff09;&#xff0c;點擊“確定”。確認刪除操作&…

Rabbitmq Direct Exchange(直連交換機)多個消費者,配置相同的key ,隊列,可以保證只有一個消費者消費嗎

思考可以保證消費不被重復消費&#xff0c;因為通過輪詢一個消息只會投遞給一個消費者。但是不是一個消費者消費&#xff0c;而是多個輪詢消費在 RabbitMQ 中&#xff0c;如果多個消費者&#xff08;Consumers&#xff09;同時訂閱 同一個隊列&#xff08;Queue&#xff09;&am…

設計模式是什么呢?

1.掌握設計模式的層次第一層&#xff1a;剛剛學編程不久&#xff0c;聽說過什么是設計模式。第二層&#xff1a;有很長時間的編程經驗&#xff0c;自己寫過很多代碼&#xff0c;其中用到了設計模式&#xff0c;但是自己不知道。第三層&#xff1a;學習過設計模式&#xff0c;發…

ThreadLocal使用詳解-從源碼層面分析

從demo入手看效果 代碼Demostatic ThreadLocal tl1 new ThreadLocal();static ThreadLocal tl2 new ThreadLocal();static ThreadLocal tl3 new ThreadLocal();public static void main(String[] args) {tl1.set("123");tl2.set("456");tl3.set("4…