3.Spring Cloud LoadBalancer 入門與使用
- 1.什么是 LoadBalancer?
- 1.1負載均衡分類
- 1.2 常見負載均衡策略
- 2.為什么要學 LoadBalancer?
- 3.如何使用?
- 4.默認負載均衡策略
- 5.隨機負載均策略
- 5.1 創建隨機負載均衡器
- 5.2 設置隨機負載均衡器 (局部設置)
- 5.3 設置全局負載均衡器
- 6.Nacos 權重負載均器
- 6.1 創建 Nacos 負載均衡器
- 6.2 設置負載均衡器
- 7.自定義負載均衡器
- 7.1 創建自定義負載均衡器
- 7.2 封裝自定義負載均衡器
- 7.3 設置自定義負載均器
- 8.緩存
- 關閉緩存
- 9.執行原理
- 底層執行原理
- 1. `ServiceInstanceListSupplier`
- 2. `LoadBalancerClient`
- 3. `LoadBalancer`
- 執行流程
- 源碼示例
- 總結
1.什么是 LoadBalancer?
LoadBalancer(負載均衡器)是一種網絡設備或軟件機制,用于分發傳入的網絡流量負載(請求)到多個后端目標服務器上,從而實現系統資源的均衡利用和提高系統的可用性和性能。
1.1負載均衡分類
負載均衡分為服務器端負載均衡和客戶端負載均衡。
- 服務器端負載均衡指的是存放在服務器端的負載均衡器,例如 Nginx、HAProxy、F5 等.
- 客戶端負載均衡指的是嵌套在客戶端的負載均衡器,例如 Ribbon、Spring Cloud LoadBalancer。
1.2 常見負載均衡策略
但無論是服務器端負載均衡和客戶端負載均衡,它們的負載均衡策略都是相同的,因為負載均衡策略本質上是一種思想。
常見的負載均衡策略有以下幾個:
- 輪詢(Round Robin):輪詢策略按照順序將每個新的請求分發給后端服務器,依次循環。這是一種最簡單的負載均衡策略,適用于后端服務器的性能相近,且每個請求的處理時間大致相同的情況。
- 隨機選擇(Random):隨機選擇策略隨機選擇一個后端服務器來處理每個新的請求。這種策略適用于后端服2務器性能相似,且每個請求的處理時間相近的情況,但不保證請求的分發是均的。
- 最少連接(Least Connections):最少連接策略將請求分發給當前連接數最少的后端服務器。這可以確保負載均衡在后端服務器的連接負載上均衡,但需要維護連接計數。
- IP 哈希(IP Hash):IP 哈希策略使用客戶端的 IP 地址來計算哈希值,然后將請求發送到與哈希值對應的后端服務器。這種策略可用于確保來自同一客戶端的請求都被發送到同一臺后端服務器,適用于需要會話保持的情況。
- 加權輪詢(Weighted Round Robin):加權輪詢策略給每個后端服務器分配一個權重值,然后按照權重值比例來分發請求。這可以用來處理后端服務器性能不均衡的情況,將更多的請求分發給性能更高的服務器。
- 加權隨機選擇(Weighted Random):加權隨機選擇策略與加權輪詢類似,但是按照權重值來隨機選擇后端服務器。這也可以用來處理后端服務器性能不均衡的情況,但是分發更隨機。
- 最短響應時間(Least Response Time):最短響應時間策略會測量每個后端服務器的響應時間,并將請求發送到響應時間最短的服務器。這種策略可以確保客戶端獲得最快的響應,適用于要求低延遲的應用。
2.為什么要學 LoadBalancer?
作為早期版本中內置的負載均衡器 Ribbon,在 Spring Cloud 2020.0.0 中已經被移除了,更新日志詳見,https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-2020.0-Release-Notes取而代之的是 Spring Cloud LoadBalancer,并日它也是 Spring cloud 官方提供的負載均衛器,所以咱們的課程就要學習最新最主流的機制棧,而 Spring Cloud LoadBalancer 則是繞不過去的必學知識。
3.如何使用?
在項目中添加 Spring Cloud OpenFeign 和注冊中心如 Nacos 之后,再添加 Spring Cloud LoadBalancer 則會在進行接口調用時直接使用 Spring Cloud LoadBalancer。
4.默認負載均衡策略
Spring Cloud LoadBalancer 負載均衡策略默認的是輪詢,這一點可以通過 Spring Cloud LoadBalancer 的配置類LoadBalancerClientConfiguration 中發現,它的部分源碼如下:
public class LoadBalancerClientConfiguration {private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;@Bean@ConditionalOnMissingBeanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);}
繼續查看 RoundRobinLoadBalancer 核心實現源碼如下:
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + serviceId);}return new EmptyResponse();}// Do not move position when there is only 1 instance, especially some suppliers// have already filtered instancesif (instances.size() == 1) {return new DefaultResponse(instances.get(0));}// Ignore the sign bit, this allows pos to loop sequentially from 0 to// Integer.MAX_VALUEint pos = this.position.incrementAndGet() & Integer.MAX_VALUE;ServiceInstance instance = instances.get(pos % instances.size());return new DefaultResponse(instance);}
5.隨機負載均策略
Spring Cloud LoadBalancer 內置了兩種負載均衡策略
- 輪詢負載均衡策略,默認負載均衡策略。
- 隨機負載均衡策略
而要實現隨機負載均衡策略的步驟如下:
- 創建隨機負載均衡策略。
- 設置隨機負載均衡策略。
5.1 創建隨機負載均衡器
public class RandomLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty("loadbalancer.client.name");return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class), name);}
}
5.2 設置隨機負載均衡器 (局部設置)
package com.example.consumer.service;import com.example.consumer.config.CustomLoadBalancerConfig;
import com.example.consumer.config.NacosLoadBalancerConfig;
import com.example.consumer.config.RandomLoadBalancerConfig;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;@Service
@FeignClient("loadbalancer-service")
// 設置局部負載均衡策略
@LoadBalancerClient(name = "loadbalancer-service",configuration = RandomLoadBalancerConfig.class)
public interface UserService {@RequestMapping("/user/getname")String getName(@RequestParam("id") Integer id);
}
5.3 設置全局負載均衡器
package com.example.consumer;import com.example.consumer.config.CustomLoadBalancerConfig;
import com.example.consumer.config.RandomLoadBalancerConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableFeignClients // 開啟 Openfeign
// 設置全局的負載均衡策略
@LoadBalancerClients(defaultConfiguration =RandomLoadBalancerConfig.class)
public class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}}
6.Nacos 權重負載均器
Nacos 中支持兩種負載均衡器,一種是權重負載均衡器,另一種是第三方 CMDB(地域就近訪問)標簽負載均後器,我們可以將 Spring Cloud Loadbalancer 直接配置為 Nacos 的負載均衡器,它默認就是權重負載均衡策略。它的配置有以下兩步:
- 創建 Nacos 負載均衡器
- 設置負載均衡器
6.1 創建 Nacos 負載均衡器
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerConfig.class)
public class NacosLoadBalancerConfig {@Resourceprivate NacosDiscoveryProperties nacosDiscoveryProperties;@Beanpublic ReactorLoadBalancer<ServiceInstance> nacosLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty("loadbalancer.client.name");return new NacosLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class), name,nacosDiscoveryProperties);}
}
6.2 設置負載均衡器
@SpringBootApplication
@EnableFeignClients // 開啟 Openfeign
// 設置全局的負載均衡策略
@LoadBalancerClients(defaultConfiguration =NacosLoadBalancerConfig.class)
public class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}}
7.自定義負載均衡器
實現自定義負載均衡策略需要以下 3步:
- 創建自定義負載均衡器
- 封裝自定義負載均衡器
- 為服務設置自定義負載均衡器
7.1 創建自定義負載均衡器
package com.example.consumer.config;import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import reactor.core.publisher.Mono;import java.util.List;public class CustomLoadBalancer implements ReactorServiceInstanceLoadBalancer {private static final Log log = LogFactory.getLog(RandomLoadBalancer.class);private final String serviceId;private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;public CustomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {this.serviceId = serviceId;this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;}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 {// 核心:自定義隨機策略// 獲取 Request 對象ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String ipAddress = request.getRemoteAddr();System.out.println("用戶 IP:" + ipAddress);int hash = ipAddress.hashCode();// 自定義負載均衡策略【這行代碼是關鍵】int index = hash % instances.size();// 得到服務實例方法ServiceInstance instance = (ServiceInstance) instances.get(index);return new DefaultResponse(instance);}}
}
7.2 封裝自定義負載均衡器
public class CustomLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> customLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty("loadbalancer.client.name");return new CustomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class), name);}
}
7.3 設置自定義負載均器
@SpringBootApplication
@EnableFeignClients // 開啟 Openfeign
// 設置全局的負載均衡策略
@LoadBalancerClients(defaultConfiguration =CustomLoadBalancerConfig.class)
public class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}}
8.緩存
Spring Cloud LoadBalancer 在獲取實例時有兩種選擇:
- 即時獲取:每次從注冊中心得到最新健康的實例,效果好、開銷太大。
- 緩存服務列表:每次得到服務列表之后,緩存一段時間,這樣既能保證性能,同時也能兼容一定的及時性。而 Spring Cloud LoadBalancer 中默認開啟了緩存服務列表的功能。Spring
Cloud LoadBalancer 默認緩存的重要特性有兩項:
- 緩存的過期時間為 35s。
- 緩存保存個數為 256 個
我們可以通過以下配置來改變這些配置:
關閉緩存
loadbalancer:cache:enabled: true # 關閉 loadbalancer 緩存ttl: 10 # 緩存存活時間capacity: 1000 # 緩存存儲容量
9.執行原理
OpenFeign 底層是通過 HTTP 客戶端對象 RestTemplate 實現接口請求的,而負載均衡器的作用只是在請求客戶端發送請求之前,得到一個服務的地址給到 RestTemplate 對象,而 Spring Cloud LoadBalancer 的整體類圖如下:
通過查看 Spring Cloud LoadBalancer 源碼我們可以發現,@LoadBalanced 注解出 spring-cloud-commons 實現查看實現邏輯我們發現, spring-cloud-commons 存在自動配置類 LoadBalancerAutoConfiquration,當滿足條件時將自動創建 LoadBalancerInterceptor 并注入到 RestTemplate 中,部分源碼如下:
Spring Cloud LoadBalancer 是 Spring Cloud 提供的一種客戶端負載均衡解決方案,用于替代 Netflix Ribbon。它通過將負載均衡邏輯從服務端移到客戶端,使得每個客戶端實例都可以獨立地選擇要調用的服務實例,從而實現更靈活和高效的負載均衡。
底層執行原理
Spring Cloud LoadBalancer 的核心組件包括 ServiceInstanceListSupplier
、LoadBalancerClient
和 LoadBalancer
。下面結合源碼來詳細說明其執行原理。
1. ServiceInstanceListSupplier
ServiceInstanceListSupplier
是一個接口,用于提供服務實例列表。它的實現類負責從服務注冊中心(如 Eureka、Consul 等)獲取可用的服務實例列表。
public interface ServiceInstanceListSupplier {Flux<List<ServiceInstance>> get();
}
Flux
是 Reactor 庫中的一個類,表示一個異步序列。ServiceInstanceListSupplier
的 get
方法返回一個 Flux
,它會異步地提供服務實例列表。
2. LoadBalancerClient
LoadBalancerClient
是一個接口,定義了負載均衡客戶端的基本操作。它的主要方法是 choose
,用于選擇一個服務實例。
public interface LoadBalancerClient {<T> ServiceInstance choose(String serviceId, Request<T> request);
}
choose
方法接受服務 ID 和請求信息,返回一個 ServiceInstance
對象,表示選擇的服務實例。
3. LoadBalancer
LoadBalancer
是負載均衡的核心接口,定義了負載均衡的策略。它的主要方法是 choose
,用于根據負載均衡策略選擇一個服務實例。
public interface LoadBalancer<T> {Mono<Response<T>> choose(Request request);
}
choose
方法返回一個 Mono<Response<T>>
,其中 Mono
是 Reactor 庫中的另一個類,表示一個異步的單值序列。
執行流程
-
獲取服務實例列表:
ServiceInstanceListSupplier
從服務注冊中心獲取可用的服務實例列表,并返回一個Flux<List<ServiceInstance>>
。
-
選擇服務實例:
LoadBalancer
使用負載均衡策略(如輪詢、隨機等)從服務實例列表中選擇一個服務實例。LoadBalancerClient
調用LoadBalancer
的choose
方法,獲取選擇的服務實例。
-
執行請求:
LoadBalancerClient
使用選擇的服務實例執行請求,并返回結果。
源碼示例
以下是一個簡單的 ServiceInstanceListSupplier
實現示例:
public class SimpleServiceInstanceListSupplier implements ServiceInstanceListSupplier {private final List<ServiceInstance> instances;public SimpleServiceInstanceListSupplier(List<ServiceInstance> instances) {this.instances = instances;}@Overridepublic Flux<List<ServiceInstance>> get() {return Flux.just(instances);}
}
以下是一個簡單的 LoadBalancer
實現示例:
public class RoundRobinLoadBalancer implements LoadBalancer<ServiceInstance> {private final AtomicInteger position;private final ServiceInstanceListSupplier supplier;public RoundRobinLoadBalancer(ServiceInstanceListSupplier supplier) {this.supplier = supplier;this.position = new AtomicInteger(0);}@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {return supplier.get().next().map(instances -> {if (instances.isEmpty()) {return new EmptyResponse();}int pos = Math.abs(this.position.incrementAndGet());ServiceInstance instance = instances.get(pos % instances.size());return new DefaultResponse(instance);});}
}
總結
Spring Cloud LoadBalancer 通過 ServiceInstanceListSupplier
獲取服務實例列表,通過 LoadBalancer
選擇服務實例,并通過 LoadBalancerClient
執行請求。其核心思想是將負載均衡邏輯從服務端移到客戶端,使得每個客戶端實例都可以獨立地選擇要調用的服務實例,從而實現更靈活和高效的負載均衡。