Spring Cloud系列—LoadBalance負載均衡

上篇文章:

Spring Cloud系列—Eureka服務注冊/發現https://blog.csdn.net/sniper_fandc/article/details/149937589?fromshare=blogdetail&sharetype=blogdetail&sharerId=149937589&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

目錄

1 如何在IDEA中啟動一個服務的多個實例

2 負載均衡

3 Spring Cloud LoadBalancer實現負載均衡

3.1 添加注解@LoadBalanced

3.2 修改遠程調用的ip:端口號為服務名稱

4 Spring Cloud LoadBalancer負載均衡策略

5 Spring Cloud LoadBalancer負載均衡原理


????????在Eureka篇章中,使用了如下代碼獲取服務的實例:

List<ServiceInstance> productService = discoveryClient.getInstances("product-service");EurekaServiceInstance serviceInstance = (EurekaServiceInstance) productService.get(0);

????????由于只有一個服務實例,因此并不會有問題,但是如果一個服務有多個實例,就會出現問題。

1 如何在IDEA中啟動一個服務的多個實例

????????點擊頁面下方的Services:

????????點擊Add service,選擇正在運行的SpringBoot服務:

????????右鍵要復制實例的服務,點擊復制:

????????在打開的界面點擊Modify options,選擇Add VM options:

????????輸入-Dserver.port=端口號,這里的端口號注意不要重復,之后選中創建的實例右鍵運行即可:

2 負載均衡

????????創建多個實例后,多次訪問接口就會出現始終訪問端口號為同一個的實例,這是因為服務發現時Eureka給我們提供隨機的服務列表,但是每次都只獲取其中下標為0的服務實例,這就會導致某個實例負載過大,因此需要負載均衡。

????????如果不借助組件,可以用hash取余的方式來輪詢訪問每個服務實例:

private static AtomicInteger atomicInteger = new AtomicInteger(1);private static List<ServiceInstance> instances;@PostConstructpublic void init(){//根據應用名稱獲取服務列表instances = discoveryClient.getInstances("product-service");}public OrderInfo selectOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectOrderById(orderId);//String url = "http://127.0.0.1:8081/product/"+ orderInfo.getProductId();//服務可能有多個, 輪詢獲取實例int index = atomicInteger.getAndIncrement() % instances.size();ServiceInstance instance =instances.get(index);log.info(instance.getInstanceId());//拼接urlString url = instance.getUri()+"/product/"+ orderInfo.getProductId();ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}

????????這里把discoveryClient.getInstances()放到了方法外面,類加載時只獲取一次,防止每次獲取的服務列表順序都不一樣,同時節省網絡資源。由于多線程環境下,為避免線程安全問題,使用原子類來計算hash取余。這種方式就是一種負載均衡,是一種客戶端負載均衡。

????????但是上述代碼有一些不足之處:服務一旦啟動,服務發現一次,其余時間不再服務發現,因此對于服務的注冊和下線是無感知的。于是需要一些專業實現負載均衡的組件,分為客戶端負載均衡和服務端負載均衡:

????????服務端負載均衡:在服務端進行負載均衡算法分配。比如使用Nginx作為負載均衡器,請求先進入Nginx再由Nginx進行負載均衡算法選擇服務來進行訪問。

????????客戶端負載均衡:由客戶端服務發現后,根據負載均衡算法選擇一個服務,并向該服務發送請求。比如Spring Cloud LoadBalancer(Spring Cloud維護)。

3 Spring Cloud LoadBalancer實現負載均衡

3.1 添加注解@LoadBalanced

????????在負責遠程調用的對象restTemplate上添加@LoadBalanced注解,表示客戶端調用時開啟負載均衡(即客戶端負載均衡):

@Configurationpublic class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}}

3.2 修改遠程調用的ip:端口號為服務名稱

@Servicepublic class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;public OrderInfo selectOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectOrderById(orderId);//負載均衡String url = "http://product-service/product/" + orderInfo.getProductId();ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}}

????????多次發送請求,發現請求被負載均衡到了各個服務上:

4 Spring Cloud LoadBalancer負載均衡策略

????????LoadBalancer默認采用輪詢方式進行負載均衡,但是也支持隨機選擇策略。要使用隨機選擇策略,需要自定義負載均衡策略器:

public class LoadBalancerConfig {@BeanReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);System.out.println("==============" + name);return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class), name);}}

????????注意:該策略器不能加@Configuration注解,并且要在Spring組件掃描范圍中(即默認和啟動類同一級目錄下)。

????????接著,在RestTemplate配置類上面添加@LoadBalancerClient注解(一個服務提供者使用)或@LoadBalancerClients注解(多個服務提供者使用):

@LoadBalancerClient(name = "product-service", configuration = LoadBalancerConfig.class)@Configurationpublic class BeanConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}}

????????@LoadBalancerClient的name表示服務名稱,configuration則是定義的負載均衡策略器。

5 Spring Cloud LoadBalancer負載均衡原理

????????LoadBalancer最關鍵的源碼是LoadBalancerInterceptor類,該類定義攔截器,將所有請求進行攔截并解析處理。具體的調用流程圖如下:

????????具體是LoadBalancerInterceptor類的intercept()發揮作用:

????public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {URI originalUri = request.getURI();String serviceName = originalUri.getHost();//解析URL是否合法(.-等連接方式)Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);//execute()方法根據服務名稱來對請求進行增強(負載均衡)return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));}

????????execute()的實現是BlockingLoadBalancerClient類,具體作用就是根據服務實例名稱(serviceId)來服務發現,并選擇合適的負載均衡策略來選擇對應的服務實例:

????public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {String hint = this.getHint(serviceId);LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter(request, this.buildRequestContext(request, hint));Set<LoadBalancerLifecycle> supportedLifecycleProcessors = this.getSupportedLifecycleProcessors(serviceId);supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onStart(lbRequest);});//choose()是核心方法,就是獲取服務實例并根據負載均衡策略來返回具體請求的實例。ServiceInstance serviceInstance = this.choose(serviceId, lbRequest);if (serviceInstance == null) {supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, new EmptyResponse()));});throw new IllegalStateException("No instances available for " + serviceId);} else {return this.execute(serviceId, serviceInstance, lbRequest);}}

????????這個choose()方法也是BlockingLoadBalancerClient類實現的,內部調用了ReactiveLoadBalancer接口的choose()方法來進行負載均衡策略的選擇:

????public <T> ServiceInstance choose(String serviceId, Request<T> request) {//獲取服務實例列表loadBalancer,也就是負載均衡器ReactiveLoadBalancer<ServiceInstance> loadBalancer = this.loadBalancerClientFactory.getInstance(serviceId);if (loadBalancer == null) {return null;} else {//根據負載均衡算法選擇合適的實例Response<ServiceInstance> loadBalancerResponse = (Response)Mono.from(loadBalancer.choose(request)).block();return loadBalancerResponse == null ? null : (ServiceInstance)loadBalancerResponse.getServer();}}

????????loadBalancer.choose()的choose()方法是ReactiveLoadBalancer接口的choose()方法,該方法的實現有RandomLoadBalancer類實現的方法和RoundRobinLoadBalancer類實現的方法,這兩個類實現的choose()方法分別對應隨機選擇策略和輪詢策略。

????????在RandomLoadBalancer類中,choose()方法調用processInstanceResponse()方法,processInstanceResponse()調用getInstanceResponse()方法,最終在getInstanceResponse()方法可以看到通過隨機數來選擇隨機的服務實例進行訪問,即隨機選擇策略:

????public Mono<Response<ServiceInstance>> choose(Request request) {ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);return supplier.get(request).next().map((serviceInstances) -> {return this.processInstanceResponse(supplier, serviceInstances);});}private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + this.serviceId);}return new EmptyResponse();} else {//此處就是隨機選擇策略最關鍵的幾行代碼int index = ThreadLocalRandom.current().nextInt(instances.size());ServiceInstance instance = (ServiceInstance)instances.get(index);return new DefaultResponse(instance);}}

????????RoundRobinLoadBalancer類的choose方法也采用了一樣的方法調用鏈,最終在getInstanceResponse()方法中,實現了本文的“負載均衡”部分的hash取余來輪詢選擇服務實例的方式:

????public Mono<Response<ServiceInstance>> choose(Request request) {ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);return supplier.get(request).next().map((serviceInstances) -> {return this.processInstanceResponse(supplier, serviceInstances);});}private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + this.serviceId);}return new EmptyResponse();} else if (instances.size() == 1) {return new DefaultResponse((ServiceInstance)instances.get(0));} else {//通過hash取余的方式來輪詢選擇服務實例int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size());return new DefaultResponse(instance);}}

下篇文章:

Spring Cloud系列—Nacos服務注冊/發現https://blog.csdn.net/sniper_fandc/article/details/149938785?fromshare=blogdetail&sharetype=blogdetail&sharerId=149938785&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

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

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

相關文章

如何使用 pnpm創建Vue 3 項目

? 一、什么是 pnpm&#xff1f; pnpm 是一種更快、更高效的 Node 包管理工具&#xff0c;替代 npm 或 yarn&#xff0c;具有&#xff1a; 更快的安裝速度更節省磁盤空間&#xff08;包復用&#xff09;嚴格的依賴管理二、使用 pnpm 創建 Vue 項目的完整流程 ? 第一步&#xf…

Vite vs. vue-cli 創建 Vue 3 項目的區別與使用場景

Vite vs. vue-cli 創建 Vue 3 項目的區別與使用場景 Vite 和 vue-cli 都是 Vue 官方推薦的腳手架工具&#xff0c;但它們的架構、構建方式和適用場景有所不同。以下是它們的對比&#xff1a;1. 核心區別對比項Vite (推薦&#x1f525;)vue-cli (傳統)構建工具基于 ESM Rollup基…

VC6800智能相機:賦能智能制造,開啟AI視覺新紀元

在工業自動化與智能化浪潮奔涌的今天&#xff0c;精準、高效、智能的視覺檢測已成為提升生產力和品質的關鍵核心。VC6800智能相機應運而生&#xff0c;它不僅僅是一部相機&#xff0c;更是一個集強大視覺硬件與前沿AI算法于一身的 “工業智眼”&#xff0c;正深刻改變著各個領域…

(Python)Python爬蟲入門教程:從零開始學習網頁抓取(爬蟲教學)(Python教學)

一、爬蟲基礎概念 什么是爬蟲&#xff1f; 網絡爬蟲&#xff08;Web Crawler&#xff09;是一種自動獲取網頁內容的程序&#xff0c;它像蜘蛛一樣在互聯網上"爬行"&#xff0c;收集和提取數據。 爬蟲應用場景&#xff1a; 搜索引擎&#xff08;Google、百度&#…

dify前端源碼部署詳細教程

這兩天突發奇想&#xff0c;能不能dify源碼部署我只部署個前端&#xff0c;后端、數據庫什么的還是原來docker部署dify的本地部署和遇到的問題。按邏輯來說應該是行得通的&#xff0c;我就親自操作了下試下。 我這邊就以我以前使用docker部署好的1.3.1版本為例。docker安裝參考…

Web地圖服務規范,WMS服務是什么

Web地圖服務規范&#xff0c;WMS服務是什么&#xff1f; WMS&#xff0c;全稱 Web Map Service (網絡地圖服務)&#xff0c;是有OGC(開放地理空間信息聯盟)制定的一項標準化協議。他的核心功能是允許客戶端&#xff08;比如網頁瀏覽器或者GIS桌面軟件&#xff09;通過互聯網或者…

北京手機基站數據分享:9.3萬點位+雙格式,解鎖城市通信「基礎設施地圖」

今天分享的是——??2023年7月北京市手機基站數據&#xff08;shpcsv雙格式&#xff09;??。92,785個基站點位&#xff08;覆蓋全市16區&#xff09;&#xff0c;WGS84坐標系直接能用&#xff0c;shp格式適配GIS軟件&#xff0c;csv格式方便Excel/Pandas分析&#xff01;文末…

Druid學習筆記 01、快速了解Druid中SqlParser實現

文章目錄前言介紹Druid代碼目錄介紹模塊一&#xff1a;Parser模塊二&#xff1a;Druid_SQL_AST在Druid SQL Parser中有哪些AST節點類型?熟悉常用的AST節點組成常用的SQLExpr有哪些&#xff1f;常用的SQLStatemment&#xff1f;SQLTableSourceSQLSelect & SQLSelectQuerySQ…

Rust中生命周期的理解與應用

在學習Rust編程語言時,理解生命周期(Lifetime)是非常關鍵的,因為它直接影響到代碼的安全性和性能。今天我們來深入探討Rust中的一個常見問題——生命周期的誤解和正確應用,結合實際代碼實例來說明。 生命周期的基本概念 Rust中的生命周期是用來確保引用(Reference)在其…

智慧感知新體驗:英飛凌雷達在智能家居的創新應用

隨著智慧家居快速發展&#xff0c;感知技術成為實現高效、便捷生活的關鍵。雷達作為非接觸、高精度的感測方案&#xff0c;正在家居應用中展現出巨大潛力。 本次研討會將由英飛凌大中華區雷達應用產品經理 Tommy Wan主講&#xff0c;分享他在智能門鈴、門鎖與安防攝像頭等應用…

AI:新書預告—從機器學習避坑指南(分類/回歸/聚類/可解釋性)到大語言模型落地手記(RAG/Agent/MCP),一場耗時5+3年的技術沉淀—“代碼可跑,經驗可抄”—【一個處女座的程序猿】攜兩本AI

AI&#xff1a;新書預告—從機器學習避坑指南(分類/回歸/聚類/可解釋性)到大語言模型落地手記(RAG/Agent/MCP)&#xff0c;一場耗時53年的技術沉淀—“代碼可跑&#xff0c;經驗可抄”—【一個處女座的程序猿】攜兩本AI實戰書終于正式來了&#xff01; 導讀&#xff1a;大家好&…

數據結構:棧、隊列

一、棧和隊列與鏈表的區別1.鏈表可以在任意位置插入和刪除元素2.棧和隊列只允許在指定位置插入和刪除元素3.棧只允許在棧頂位置入棧和出棧元素3.相同點&#xff1a;表、棧、隊列都是一種線性結構&#xff08;一對一&#xff09;4.棧和隊列是一種特殊的表狀結構二、棧&#xff0…

cuda編程筆記(13)--使用CUB庫實現基本功能

CUB 是 NVIDIA 提供的 高性能 CUDA 基礎庫&#xff0c;包含常用的并行原語&#xff08;Reduction、Scan、Histogram 等&#xff09;&#xff0c;可以極大簡化代碼&#xff0c;并且比手寫版本更優化。CUB無需鏈接&#xff0c;只用包含<cub/cub.cuh>頭文件即可需要先臨時獲…

LabVIEW濾波器測控系統

?基于LabVIEW 平臺的高頻濾波器測控系統&#xff0c;通過整合控制與測試功能&#xff0c;替代傳統分離式測控模式。系統以 LabVIEW 為核心&#xff0c;借助標準化接口實現對濾波器的自動化參數調節與性能測試&#xff0c;顯著提升測試效率與數據處理能力&#xff0c;適用于高頻…

美團運維面試題及參考答案(上)

輸入一個字符串,將其轉換成數字時,需要考慮哪些情況(如字符串是否合法、是否為空、int 的范圍、是否為 16 進制等)? 將字符串轉換成數字時,需全面考慮多種邊界情況和合法性問題,具體如下: 字符串基礎狀態:首先需判斷字符串是否為空(長度為0)或僅包含空白字符(如空…

Spring-AI 深度實戰:企業級 AI 應用開發指南與 Python 生態對比(高級篇)

為什么 Spring-AI 是企業級 AI 的“隱形冠軍”&#xff1f;&#xff08;而不僅是另一個封裝庫&#xff09;在 Python 主導的 AI 世界中&#xff0c;Spring-AI 的誕生常被誤解為“Java 的跟風之作”。但真正的企業級 AI 需求&#xff08;事務一致性、分布式追蹤、安全審計&#…

OpenAI 回歸開源領域突發兩大推理模型,六強AI企業競逐加劇軍備競賽態勢!

獲悉&#xff0c;OpenAI重回開源賽道&#xff0c;奧特曼深夜官宣兩個分別名為GPT-oss-120b和GPT-oss-20b的模型將在AI軟件托管平臺Hugging Face上線&#xff0c;在用戶輸入指令后將能生成文本。兩大推理模型上線GPT-oss-120b適用于需要高推理能力的生產級和通用型場景。在核心推…

嵌入式學習硬件(一)ARM體系架構

目錄 1.SOC 2.內核架構的分類 3.馮諾依曼架構和哈佛架構 4.kernel 5.指令集 6.ARM處理器產品分類 7.編譯的四個步驟?編輯 8.RAM和ROM?編輯 9.ARM處理器工作模式 10.異常處理 11.CPSR程序狀態寄存器 1.SOC system on chip 片上系統&#xff0c;可以運行操作系統的一種高端的功…

OpenAI推出開源GPT-oss-120b與GPT-oss-20b突破性大模型,支持商用與靈活部署!

模型介紹OpenAI再次推出開源模型&#xff0c;發布了兩款突破性的GPT-oss系列大模型&#xff0c;即GPT-oss-120b和GPT-oss-20b&#xff0c;為AI領域帶來了巨大的創新和發展潛力。這兩款模型不僅在性能上與現有的閉源模型媲美&#xff0c;而且在硬件適配性上具有明顯優勢&#xf…

【Unity Plugins】使用ULipSync插件實現人物唇形模擬

一、下載插件ULipSync&#xff1a; 1. 進入Github網址&#xff1a;https://github.com/hecomi/uLipSync/releases/tag/v3.1.4 2. 點擊下載下方的unitypackage 3. 安裝使用ULipSync的相關的插件 發行者也提到了&#xff0c;在使用的時候需要在Package Manager里安裝Unity.B…