Spring Cloud負(fù)載均衡LoadBalance及實(shí)際應(yīng)用
一、負(fù)載均衡介紹
1.1、問(wèn)題描述
上篇文章中遠(yuǎn)程調(diào)用的代碼如下:
List<ServiceInstance> instances =discoveryClient.getInstances("productservice");
//服務(wù)可能有多個(gè), 獲取第?個(gè)
EurekaServiceInstance instance = (EurekaServiceInstance)instances.get(0);
- 根據(jù)應(yīng)用名稱獲取了服務(wù)實(shí)例列表
- 從列表中選擇了一個(gè)服務(wù)實(shí)例
**思考:**如果一個(gè)服務(wù)對(duì)應(yīng)多個(gè)實(shí)例呢? 流量是否可以合理的分配到多個(gè)實(shí)例呢?
現(xiàn)象觀察:
我們?cè)賳?dòng)2個(gè)product-service實(shí)例
選中要啟動(dòng)的服務(wù), 右鍵選擇 Copy Configuration

在彈出的框中, 選擇 Modify options -> Add VM options

添加 VM options : -Dserver.port=9091
9091 為服務(wù)啟動(dòng)的端口號(hào), 根據(jù)自己的情況進(jìn)行修改

現(xiàn)在IDEA的Service窗口就會(huì)多出來(lái)?個(gè)啟動(dòng)配置, 右鍵啟動(dòng)服務(wù)就可以

同樣的操作, 再啟動(dòng)1個(gè)實(shí)例, 共啟動(dòng)3個(gè)服務(wù)

觀察Eureka, 可以看到product-service下有三個(gè)實(shí)例:

訪問(wèn)日志:
訪問(wèn): http://127.0.0.1:8080/order/1
通過(guò)觀察日志我們發(fā)現(xiàn):請(qǐng)求多次訪問(wèn), 都是同?臺(tái)機(jī)器。這肯定不是我們想要的結(jié)果, 我們啟動(dòng)多個(gè)實(shí)例, 是希望可以分擔(dān)其他機(jī)器的負(fù)荷, 那么如何實(shí)現(xiàn)呢?
解決方案:
我們可以對(duì)上述代碼進(jìn)行簡(jiǎn)單修改:
private static AtomicInteger atomicInteger = new AtomicInteger(1);
private static List<ServiceInstance> instances;
@PostConstruct
public void init(){
//根據(jù)應(yīng)?名稱獲取服務(wù)列表
instances = discoveryClient.getInstances("product-service");
}
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
//String url = "http://127.0.0.1:9090/product/"+ orderInfo.getProductId();
//服務(wù)可能有多個(gè), 輪詢獲取實(shí)例
int index = atomicInteger.getAndIncrement() % instances.size();
ServiceInstance instance =instances.get(index);
log.info(instance.getInstanceId());
//拼接url
String url = instance.getUri()+"/product/"+ orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url,
ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}修改代碼后,我們?cè)俅螁?dòng)發(fā)現(xiàn)請(qǐng)求被均衡的分配在了不同的實(shí)例上, 這就是負(fù)載均衡。
1.2、什么是負(fù)載均衡
負(fù)載均衡(Load Balance,簡(jiǎn)稱 LB) , 是高并發(fā), 高可用系統(tǒng)必不可少的關(guān)鍵組件.
當(dāng)服務(wù)流量增大時(shí), 通常會(huì)采用增加機(jī)器的方式進(jìn)行擴(kuò)容, 負(fù)載均衡就是用來(lái)在多個(gè)機(jī)器或者其他資源中, 按照?定的規(guī)則合理分配負(fù)載.
?個(gè)團(tuán)隊(duì)最開始只有?個(gè)?, 后來(lái)隨著工作量的增加, 公司又招聘了幾個(gè)?. 負(fù)載均衡就是: 如何把工作量均衡的分配到這幾個(gè)人?上, 以提高整個(gè)團(tuán)隊(duì)的效率
1.3、負(fù)載均衡的一些實(shí)現(xiàn)
上面的例子中, 我們只是簡(jiǎn)單的對(duì)實(shí)例進(jìn)行了輪詢, 但真實(shí)的業(yè)務(wù)場(chǎng)景會(huì)更加復(fù)雜. 比如根據(jù)機(jī)器的配置進(jìn)行負(fù)載分配, 配置高的分配的流量高, 配置低的分配流量低等.
類似企業(yè)員工: 能力強(qiáng)的員工可以多承擔(dān)一些工作.服務(wù)多機(jī)部署時(shí), 開發(fā)人員都需要考慮負(fù)載均衡的實(shí)現(xiàn), 所以也出現(xiàn)了?些負(fù)載均衡器, 來(lái)幫助我們實(shí)現(xiàn)負(fù)載均衡.
負(fù)載均衡分為服務(wù)端負(fù)載均衡和客戶端負(fù)載均衡
服務(wù)端負(fù)載均衡
在服務(wù)端進(jìn)行負(fù)載均衡的算法分配.
比較有名的服務(wù)端負(fù)載均衡器是Nginx. 請(qǐng)求先到達(dá)Nginx負(fù)載均衡器, 然后通過(guò)負(fù)載均衡算法, 在多個(gè)服務(wù)器之間選擇一個(gè)進(jìn)行訪問(wèn).

客戶端負(fù)載均衡
在客戶端進(jìn)行負(fù)載均衡的算法分配.
把負(fù)載均衡的功能以庫(kù)的方式集成到客戶端, 而不再是由一臺(tái)指定的負(fù)載均衡設(shè)備集中提供.
比如Spring Cloud的Ribbon, 請(qǐng)求發(fā)送到客戶端, 客戶端從注冊(cè)中心(比如Eureka)獲取服務(wù)列表, 在發(fā)送請(qǐng)求前通過(guò)負(fù)載均衡算法選擇?個(gè)服務(wù)器,然后進(jìn)行訪問(wèn).
Ribbon是Spring Cloud早期的默認(rèn)實(shí)現(xiàn), 由于不維護(hù)了, 所以最新版本的Spring Cloud負(fù)載均衡集成的是Spring Cloud LoadBalancer(Spring Cloud官方維護(hù))

客戶端負(fù)載均衡和服務(wù)端負(fù)載均衡最大的區(qū)別在于服務(wù)清單所存儲(chǔ)的位置
二、Spring Cloud LoadBalancer
2.1、快速上手
2.1.1、使用Spring Cloud LoadBalancer實(shí)現(xiàn)負(fù)載均衡
1 . 給 RestTemplate 這個(gè)Bean添加 @LoadBalanced 注解就可以
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.修改IP端口號(hào)為服務(wù)名稱
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
//String url = "http://127.0.0.1:9090/product/"+ orderInfo.getProductId();
String url = "http://product-service/product/"+ orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url,ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
2.1.2、啟動(dòng)多個(gè)product-service實(shí)例
按照上面的方式,啟動(dòng)多個(gè)實(shí)例。
2.1.3、測(cè)試負(fù)載均衡
啟動(dòng)后,我們觀察日志,發(fā)現(xiàn)請(qǐng)求被分配到三個(gè)實(shí)例上了

2.2、負(fù)載均衡策略
負(fù)載均衡策略是?種思想, ?論是哪種負(fù)載均衡器, 它們的負(fù)載均衡策略都是相似的. Spring CloudLoadBalancer 僅?持兩種負(fù)載均衡策略: 輪詢策略 和 隨機(jī)策略
- 輪詢(Round Robin): 輪詢策略是指服務(wù)器輪流處理用戶的請(qǐng)求. 這是?種實(shí)現(xiàn)最簡(jiǎn)單, 也最常用的策略. 生活中也有類似的場(chǎng)景, 比如學(xué)校輪流值日, 或者輪流打掃衛(wèi)?.
- 隨機(jī)選擇(Random): 隨機(jī)選擇策略是指隨機(jī)選擇?個(gè)后端服務(wù)器來(lái)處理新的請(qǐng)求.
自定義負(fù)載均衡策略:
Spring Cloud LoadBalancer 默認(rèn)負(fù)載均衡策略是輪詢策略, 實(shí)現(xiàn)是 RoundRobinLoadBalancer, 如果服務(wù)的消費(fèi)者如果想采用隨機(jī)的負(fù)載均衡策略, 也非常簡(jiǎn)單.
- 定義隨機(jī)算法對(duì)象, 通過(guò) @Bean 將其加載到 Spring 容器中
此處使用Spring Cloud LoadBalancer提供的 RandomLoadBalancer
public class LoadBalancerConfig {
@Bean
ReactorLoadBalancer<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);
}
}注意: 該類需要滿足:
1 . 不用 @Configuration 注釋
2 . 在組件掃描范圍內(nèi)
- 使用 @LoadBalancerClient 或者 @LoadBalancerClients 注解在 RestTemplate 配置類上方, 使用@LoadBalancerClient 或 @LoadBalancerClients 注解, 可以對(duì)不同的服務(wù)提供方配置不同的客戶端負(fù)載均衡算法策略.由于項(xiàng)目中只有?個(gè)服務(wù)提供者, 所以使用@LoadBalancerClient
@LoadBalancerClient(name = "product-service", configuration =LoadBalancerConfig.class)
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@LoadBalancerClient 注解說(shuō)明
- name: 該負(fù)載均衡策略對(duì)哪個(gè)服務(wù)生效(服務(wù)提供方)
- configuration : 該負(fù)載均衡策略 用哪個(gè)負(fù)載均衡策略實(shí)現(xiàn).
2.3、LoadBalancer 原理
LoadBalancer 的實(shí)現(xiàn), 主要是 LoadBalancerInterceptor , 這個(gè)類會(huì)對(duì) RestTemplate 的請(qǐng)求進(jìn)行攔截, 然后從Eureka根據(jù)服務(wù)id獲取服務(wù)列表,隨后利用負(fù)載均衡算法得到真實(shí)的服務(wù)地址信息,替換服務(wù)id
我們來(lái)看看源碼實(shí)現(xiàn):
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
public 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請(qǐng)求,然后做了幾件事:
1 . request.getURI() 從請(qǐng)求中獲取uri
2 . service/product/1001 originalUri.getHost() 從uri中獲取路徑的主機(jī)名, 也就是服務(wù)id, product-service
3 . loadBalancer.execute 根據(jù)服務(wù)id, 進(jìn)行負(fù)載均衡, 并處理請(qǐng)求
后續(xù)代碼:
public class BlockingLoadBalancerClient implements LoadBalancerClient {
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);
});
//根據(jù)serviceId,和負(fù)載均衡策略, 選擇處理的服務(wù)
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);
}
}
/**
* 根據(jù)serviceId,和負(fù)載均衡策略, 選擇處理的服務(wù)
*
*/
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
//獲取負(fù)載均衡器
ReactiveLoadBalancer<ServiceInstance> loadBalancer =
this.loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
} else {
//根據(jù)負(fù)載均衡算法, 在列表中選擇?個(gè)服務(wù)實(shí)例
Response<ServiceInstance> loadBalancerResponse =
(Response)Mono.from(loadBalancer.choose(request)).block();
return loadBalancerResponse == null ? null :
(ServiceInstance)loadBalancerResponse.getServer();
}
}
}三、總結(jié)
以上就是本文全部?jī)?nèi)容,本文主要為大家介紹了負(fù)載均衡-LoadBalance以及實(shí)際應(yīng)用。感謝各位能夠看到最后,如有問(wèn)題,歡迎各位大佬在評(píng)論區(qū)指正,希望大家可以有所收獲!創(chuàng)作不易,希望大家多多支持
到此這篇關(guān)于Spring Cloud負(fù)載均衡LoadBalance及實(shí)際應(yīng)用的文章就介紹到這了,更多相關(guān)Spring Cloud負(fù)載均衡LoadBalance內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 全解析Spring Cloud之負(fù)載均衡之LoadBalance
- SpringCloud之LoadBalancer負(fù)載均衡服務(wù)調(diào)用過(guò)程
- SpringCloud負(fù)載均衡spring-cloud-starter-loadbalancer解讀
- Spring Cloud LoadBalancer 負(fù)載均衡詳解
- Spring?cloud負(fù)載均衡@LoadBalanced?&?LoadBalancerClient
- 詳解openfeign集成spring?cloud?loadbalancer實(shí)現(xiàn)負(fù)載均衡流程
- SpringCloud之loadbalancer負(fù)載均衡組件實(shí)戰(zhàn)詳解
- SpringCloud LoadBalancer自定義負(fù)載均衡器使用解析
- 詳解SpringCloud LoadBalancer 新一代負(fù)載均衡器
相關(guān)文章
MyBatis查詢數(shù)據(jù),賦值給List集合時(shí),數(shù)據(jù)缺少的問(wèn)題及解決
這篇文章主要介紹了MyBatis查詢數(shù)據(jù),賦值給List集合時(shí),數(shù)據(jù)缺少的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
解決IDEA集成Docker插件后出現(xiàn)日志亂碼的問(wèn)題
這篇文章主要介紹了解決IDEA集成Docker插件后出現(xiàn)日志亂碼的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
Java主鍵生成之@Id和@GeneratedValue使用詳解
這篇文章主要介紹了Java主鍵生成之@Id和@GeneratedValue的使用,@Id和@GeneratedValue注解就是JPA中用于定義主鍵和主鍵生成策略的關(guān)鍵注解,理解這兩個(gè)注解的使用和不同的主鍵生成策略,對(duì)于開發(fā)高效、穩(wěn)定的數(shù)據(jù)持久化應(yīng)用至關(guān)重要,需要的朋友可以參考下2025-05-05
elasticsearch索引index之engine讀寫控制結(jié)構(gòu)實(shí)現(xiàn)
這篇文章主要為大家介紹了elasticsearch索引index之engine讀寫控制結(jié)構(gòu)實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
在spring boot中使用java線程池ExecutorService的講解
今天小編就為大家分享一篇關(guān)于在spring boot中使用java線程池ExecutorService的講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03

