目錄
理解負載均衡
負載均衡的實現方式
服務端負載均衡
客戶端負載均衡
Spring Cloud LoadBalancer快速上手
常見的負載均衡策略
自定義負載均衡策略
LoadBalancer 原理
理解負載均衡
在 Spring Cloud 微服務架構中,負載均衡(Load Balance)是實現服務高可用、提高系統吞吐量的核心機制之一。它通過將請求合理分發到多個服務實例,避免單個實例過載,同時實現故障自動隔離,是服務調用鏈路中的關鍵環節。
在微服務中,一個服務通常會部署多個實例(如產品信息服務可能有product-service:9090
、product-service:9091
、product-service:9092
等)。負載均衡的核心目標是:
請求分發:將服務消費者的請求均勻分配到多個服務提供者實例,避免單點壓力過大。
故障隔離:自動排除不可用的實例(如宕機、健康檢查失敗),確保請求只發送到可用實例。
彈性伸縮支持:當服務實例擴縮容時,能自動感知并調整分發策略,無需人工干預。
負載均衡的實現方式
負載均衡分為服務端負載均衡和客戶端負載均衡
服務端負載均衡
比較有名的服務端負載均衡器是Nginx,請求先到達Nginx負載均衡器,然后通過負載均衡算法,在多個服務器之間選擇?個進行訪問。
客戶端負載均衡
把負載均衡的功能以庫的方式集成到客戶端,而不再是由?臺指定的負載均衡設備集中提供。
比如Spring Cloud的Ribbon,請求發送到客戶端,客戶端從注冊中心(比如Eureka)獲取服務列表,在發送請求前通過負載均衡算法選擇?個服務器,然后進行訪問。
Ribbon是Spring Cloud早期的默認實現,由于不維護了,所以最新版本的Spring Cloud負載均衡集成的是Spring Cloud LoadBalancer(Spring Cloud官方維護)
Spring Cloud LoadBalancer快速上手
Spring Cloud LoadBalancer 并不是一個獨立服務,而是一個 客戶端負載均衡庫。
調用流程大致是:
獲取服務名(例如調用
http://inventory-service/api/stock/1
,這里的inventory-service
就是服務名)。去服務注冊中心查找(Nacos、Eureka、Consul…)獲取該服務的所有實例地址(IP:Port)。
負載均衡策略選擇:比如輪詢、隨機、權重優先、本地優先。
發起請求到選定的實例
使用方式
給 RestTemplate
加上 @LoadBalanced
注解:
@Configuration
public class RestTemplateConfig {@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}
}
使用時直接寫服務名:
@Autowired
private RestTemplate restTemplate;
?
public OrderDO selectOrderById(Integer id) {OrderDO orderDO = orderMapper.selectOrderById(id);//這里的 product-service 不再是域名,而是注冊中心里的 服務名。String url = "http://product-service/product/"+orderDO.getProductId();//遠程調用獲取數據ProductDO productDO = restTemplate.getForObject(url, ProductDO.class);orderDO.setProductDO(productDO);return orderDO;
}
啟動服務進行測試
前置環境配置可參考Spring Cloud——服務注冊與服務發現原理與實現-CSDN博客
測試負載均衡
連續多次發起請求: http://127.0.0.1:8080/order/1
觀察product-service的日志, 會發現請求被分配到這3個實例上了
常見的負載均衡策略
Spring Cloud LoadBalancer 默認策略是 RoundRobin(輪詢)。 常見策略包括:
RoundRobin(輪詢):依次選擇服務實例,分配均勻。
Random(隨機):隨機選一個實例,適合流量比較小的場景。
Weighted(權重):根據權重選擇(Nacos 支持,可以按版本或機房區分)。
ZonePreference(區域優先):優先選擇同機房的實例,跨機房兜底。
可以通過自定義配置擴展策略。
自定義負載均衡策略
@Configuration
public class LoadBalancerConfig {@BeanReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class), name);}
}
注意: 該類需要滿足:
不用 @Configuration 注釋
在組件掃描范圍內
@LoadBalancerClient(name = "product-service", configuration = LoadBalancerConfig.class)
@Configuration
public class BeanConfig {@LoadBalanced@Beanpublic RestTemplate getRestTemplate(){return new RestTemplate();}
}
在 RestTemplate 配置類上方, 使用 @LoadBalancerClient 或 @LoadBalancerClients 注解, 可以對不同的服務提供方配置不同的客戶端負載均衡算法策略,這樣就把默認的 輪詢策略替換成了 隨機策略
@LoadBalancerClient 注解說明
name: 該負載均衡策略對哪個服務生效(服務提供方)。
configuration : 該負載均衡策略用哪個負載均衡策略實現。
LoadBalancer 原理
LoadBalancer 的實現,主要是 LoadBalancerInterceptor ,這個類會對 RestTemplate 的請求進行攔截,然后從Eureka根據服務id獲取服務列表,隨后利用負載均衡算法得到真實的服務地址信息,替換服務id。
我們來看看源碼實現:
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {// ...@Overridepublic ClientHttpResponse intercept(final HttpRequest request,final byte[] body,final ClientHttpRequestExecution execution) throws IOException {URI originalUri = request.getURI();String serviceName = originalUri.getHost();
?Assert.state(serviceName != null,"Request URI does not contain a valid hostname: " + originalUri);
?return (ClientHttpResponse) this.loadBalancer.execute(serviceName,this.requestFactory.createRequest(request, body, execution));}
}
可以看到這里的intercept方法, 攔截了用戶的HttpRequest請求,然后做了幾件事:
request.getURI() 從請求中獲取uri,也就是 http://product-service/product/1001
originalUri.getHost() 從uri中獲取路徑的主機名,也就是服務id,product-service
loadBalancer.execute 根據服務id,進行負載均衡,并處理請求。
public class BlockingLoadBalancerClient implements LoadBalancerClient {
?@Overridepublic <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));
?// 根據 serviceId 和負載均衡策略選擇處理的服務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);}}
?/*** 根據 serviceId 和負載均衡策略選擇一個服務實例*/@Overridepublic <T> ServiceInstance choose(String serviceId, Request<T> request) {// 獲取負載均衡器ReactiveLoadBalancer<ServiceInstance> loadBalancer =this.loadBalancerClientFactory.getInstance(serviceId);
?if (loadBalancer == null) {return null;} else {// 根據負載均衡算法,在列表中選擇一個服務實例Response<ServiceInstance> loadBalancerResponse =(Response<ServiceInstance>) Mono.from(loadBalancer.choose(request)).block();
?return loadBalancerResponse == null ? null : loadBalancerResponse.getServer();}}
}