javascript
SpringCloud[04]Ribbon负载均衡服务调用
文章目錄
- Ribbon負載均衡服務調用
- 1. 概述
- 1. Ribbon是什么
- 2. Ribbon能做什么
- 2. Ribbon負載均衡演示
- 1. 架構說明
- 2. Ribbon的POM依賴
- 3. RestTemplate的使用
- 3. Ribbon核心組件IRule
- 1. IRule:根據特定算法從服務列表中選取一個要訪問的服務
- 2. 如何替換負載均衡算法
- 4. Ribbon負載均衡算法
- 1. 默認負載均衡算法(輪詢)原理
- 2. 源碼分析
- 3. 自己實現輪詢負載均衡算法
Ribbon負載均衡服務調用
1. 概述
1. Ribbon是什么
SpringCloud Ribbon是基于Netflix Ribbon實現的一套客戶端,是負載均衡的工具。
簡單的說,Ribbon是Netflix發布的開源項目,主要功能是提供客戶端的軟件復雜均衡算法和服務調用。Ribbon客戶端組件提供一系列完事的配置項如連接超時、重試等。簡單的說,就是在配置文件中列出Load Balancer(負載均衡簡稱LB)后面所有的及其,Ribbon會自動的幫助你基于某種規則(如簡單輪詢,隨機連接等)去連接這些機器。也可以使用Ribbon實現自定義的負載均衡算法。
2. Ribbon能做什么
主要是負載均衡(LB):所謂負載均衡,簡單的說就是將用戶的請求平攤的分配到多個服務上,從而達到系統的HA(High Available高可用),常見的負載均衡有軟件Nginx、LVS,硬件F5等。
Ribbon本地負載均衡客戶端和Nginx服務端負載均衡的區別:
- Nginx是服務器負載均衡,客戶端所有請求都會交給Nginx,然后由Nginx實現轉發請求,即負載均衡是由服務端實現的。
- Ribbon是本地負載均衡,在調用微服務接口時候,會在注冊中心上獲取注冊信息服務列表之后緩存到JVM本地,從而在本地實現RPC遠程服務調用技術。
負載均衡又分為兩類,分別可以對應于Nginx和Ribbon:
- 集中式LB:即在服務的消費方和提供方之間使用獨立的LB設施(可以是硬件,如F5,也可以是軟件,如Nginx),由該設施負責把訪問請求通過某種策略轉發至服務的提供方。
- 進程內LB:將LB邏輯集成到消費方,消費方從服務注冊中心獲知有哪些地址可用,然后自己再從這些地址中選擇出一個合適的服務器,Ribbon就屬于進程內LB,它只是一個類庫,集成于消費方進程,消費方通過它來獲取到服務提供方的地址。
Ribbon實際上就是負載均衡+RestTemplate調用
2. Ribbon負載均衡演示
1. 架構說明
其架構可以用下圖表示:
Ribbon其實就是一個軟負載均衡的客戶端組件,他可以和其他所需請求的客戶端結合使用,和eureka結合只是其中的一個實例。Ribbon在工作的時候分兩步:
其中Ribbon提供了多種的負載均衡策略,如輪詢、隨機和根據響應時間加強等。
2. Ribbon的POM依賴
在POM文件中我們引入了如下依賴:
<!--eureka-client--> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>點開該依賴的源碼,我們發現,事實上改依賴內部已經引入了Ribbon,其引入Ribbon的源碼如下:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId><version>2.2.1.RELEASE</version><scope>compile</scope> </dependency>我們在Maven的依賴中也可以看到,在引入 spring-cloud-starter-netflix-eureka-client 的同時我們就已經引入了 **spring-cloud-starter-netflix-ribbon **,所以我們沒必要單獨添加Ribbon的依賴。
3. RestTemplate的使用
其官方說明可以在RestTemplate官方API查看,下面簡要說明其主要方法
-
getForObject方法/getForEntity方法
getForObject:返回對象為響應體數據轉化成的對象,基本上可以理解為Json對象。
getForEntity:返回對象為ResponseEntity對象,包含了響應中的一些重要信息,比如響應頭、響應狀態碼、響應體等。
@GetMapping("/consumer/payment/get/{id}")public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);}@GetMapping("/consumer/payment/getForEntity/{id}")public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);if (entity.getStatusCode().is2xxSuccessful()) {log.info("======" + entity.getStatusCode() + "\t" + entity.getHeaders());return entity.getBody(); //返回請求體} else {return new CommonResult<>(444, "操作失敗");}}在后臺控制臺也輸出了狀態碼和請求頭的如下日志:
2020-05-20 20:54:02.961 INFO 19552 --- [p-nio-80-exec-3] c.s.s.controller.OrderController : ======200 OK [Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Wed, 20 May 2020 12:54:02 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]推薦使用getForObject,因為現在Json是大勢所趨啊。
3. Ribbon核心組件IRule
1. IRule:根據特定算法從服務列表中選取一個要訪問的服務
IRule是一個接口,其源碼如下:
package com.netflix.loadbalancer;/*** Interface that defines a "Rule" for a LoadBalancer. A Rule can be thought of* as a Strategy for loadbalacing. Well known loadbalancing strategies include* Round Robin, Response Time based etc.* * @author stonse* */ public interface IRule{/** choose one alive server from lb.allServers or* lb.upServers according to key* * @return choosen Server object. NULL is returned if none* server is available */public Server choose(Object key);public void setLoadBalancer(ILoadBalancer lb);public ILoadBalancer getLoadBalancer(); }以下是IRule接口的部分實現,這些實現分別對應了若干負載均衡算法
以下簡要說明7種主要的負載均衡算法,這些負載均衡算法均是抽象類com.netflix.loadbalancer.AbstractLoadBalancerRule的實現,而給抽象類實現了IRule接口:
- com.netflix.loadbalancer.RoundRobinRule:輪詢,為默認的負載均衡算法。
- com.netflix.loadbalancer.RandomRule:隨機。
- com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule(輪詢)的策略獲取服務,如果獲取服務失敗則在指定時間內進行重試,獲取可用的服務。
- com.netflix.loadbalancer.WeightedResponseTimeRule:對RoundRobinRule的擴展,響應速度越快的實例選擇權重越大,越容易被選擇。
- com.netflix.loadbalancer.BestAvailableRule:先過濾掉由于多次訪問故障而處于斷路器跳閘狀態的服務,然后選擇一個并發量最小的服務。
- com.netflix.loadbalancer.AvailabilityFilteringRule:先過濾掉故障實例,再選擇并發較小的實例。
- com.netflix.loadbalancer.ZoneAvoidanceRule:復合判斷Server所在區域的性能和Server的可用性選擇服務器。
2. 如何替換負載均衡算法
-
首先我們應該明確是服務消費方采用輪詢算法來訪問同一服務提供方的不同微服務實例,所以我們應該在服務消費方80方的微服務中添加輪詢算法配置類,在添加配置類時,有必須要注意的點,就是官方文檔明確給出了警告:這個自定義的輪詢算法配置類不能放在@ComponentScan注解所掃描的當前包下以及子包下,否則自定義的這個配置類就會被所有Ribbon客戶端所共享,就打不到特殊化定制的目的了,換句話說,如果這個配置類我們能夠被@ComponentScan注解掃描到,那么訪問所有的微服務提供方的具體實例時,我們都會采取配置類中的算法,如果要特殊化定制——即指定訪問某些微服務提供方時采用配置的輪詢算法,那么我們就應該使這個配置類讓@ComponentScan注解掃描不到,我們知道,在主啟動類的@SpringBootApplication注解中,其實這個注解包含了@SpringBootConfiguration 、@EnableAutoConfiguration、@ComponentScan這三個注解,所以我們寫的輪詢算法配置類不能和主啟動類在同一個包下,所以我們需要建新的包,實現定制輪詢算法的配置類:
package cn.sher6j.myrule;import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;/*** @author sher6j*/ @Configuration public class MySelfRule {@Beanpublic IRule myRule() {return new RandomRule(); //定義隨機負載均衡算法} }包結構的內容如下,我們可以看到,輪詢算法配置類在主啟動類的@ComponentScan掃描不到的包下:
-
然后在主啟動類中添加@RibbonClient注解
package cn.sher6j.springcloud;import cn.sher6j.myrule.MySelfRule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.ribbon.RibbonClient;/*** @author sher6j*/ @SpringBootApplication @EnableEurekaClient //訪問的微服務為CLOUD-PAYMENT-SERVICE,采用配置文件中的輪詢算法 @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class) public class OrderMain80 {public static void main(String[] args) {SpringApplication.run(OrderMain80.class);} } -
測試
如圖我們用服務消費方訪問服務提供方的微服務時,8001和8002不再交替輪詢訪問,而是隨機訪問。
4. Ribbon負載均衡算法
1. 默認負載均衡算法(輪詢)原理
輪詢負載均衡算法原理:Rest接口第幾次請求數 % 服務器集群總數量 = 實際調用服務器位置下標, 每次服務重啟后Rest接口技術從1開始。
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE")根據服務方的服務名,獲取其所有實例,如有以下實例:
| 服務名 | payment8001 | payment8002 |
| 服務地址 | 127.0.0.1:8001 | 127.0.0.1:8002 |
這兩個實例組合成一個集群,共2臺機器,集群總數為2,按照輪詢負載均衡算法原理:
請求總數為1時,1 % 2 = 1,對應下標位置是1,獲得服務地址127.0.0.1:8001
請求總數為2時,2 % 2 = 0,對應下標位置是0,獲得服務地址127.0.0.1:8002
請求總數為3時,3 % 2 = 1,對應下標位置是1,獲得服務地址127.0.0.1:8001
………………………………
2. 源碼分析
將com.netflix.loadbalancer.RoundRobinRule源碼的負載均衡算法部分分析如下(代碼中標注了中文注釋):
package com.netflix.loadbalancer;import com.netflix.client.config.IClientConfig; ……………………………… import java.util.List; import java.util.concurrent.atomic.AtomicInteger;/*** The most well known and basic load balancing strategy, i.e. Round Robin Rule.*/ public class RoundRobinRule extends AbstractLoadBalancerRule {………………………………public Server choose(ILoadBalancer lb, Object key) {if (lb == null) {log.warn("no load balancer");return null;}Server server = null;int count = 0;while (server == null && count++ < 10) {//獲得還活著的健康的服務實例(機器)即可達的,也就是Status為up的實例List<Server> reachableServers = lb.getReachableServers();//獲取所有服務實例,無論是死是活,只要注冊進服務中心即可List<Server> allServers = lb.getAllServers();//Status為up的服務實例數量int upCount = reachableServers.size();//所有服務實例的數量,對應上述原理分析中的服務器集群總數量int serverCount = allServers.size();//如果沒有可達的服務實例的話,直接報警告if ((upCount == 0) || (serverCount == 0)) {log.warn("No up servers available from load balancer: " + lb);return null;}//調用服務器位置下標 = incrementAndGetModulo(服務器集群總數)int nextServerIndex = incrementAndGetModulo(serverCount);server = allServers.get(nextServerIndex);//根據下標獲取服務實例if (server == null) {/* Transient. */Thread.yield();continue;}if (server.isAlive() && (server.isReadyToServe())) {return (server);}// Next.server = null;}if (count >= 10) {log.warn("No available alive servers after 10 tries from load balancer: "+ lb);}return server;}/*** Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.** @param modulo The modulo to bound the value of the counter.* @return The next value.*/private int incrementAndGetModulo(int modulo) {for (;;) {int current = nextServerCyclicCounter.get();int next = (current + 1) % modulo;if (nextServerCyclicCounter.compareAndSet(current, next))return next;}} }3. 自己實現輪詢負載均衡算法
首先我們將服務注冊中心(7001/7002構成集群)啟動,然后在服務提供方8001/8002中的Controller中添加功能,用來一會兒測試服務消費方80來輪詢訪問CLOUD-PAYMENT-SERVICE服務:
@GetMapping("/payment/lb")public String getPaymentLB(){return serverPort;}服務提供方的這個方法就是簡單的在頁面輸出自己的端口號,也就是我們可以在頁面區分訪問的CLOUD-PAYMENT-SERVICE服務到底對應的是8001實例還是8002實例。
啟動8001/8002,將兩個服務實例注冊進服務注冊中心后,我們再改造服務消費方80服務,分為以下四步:
-
首先我們先讓RestTemplate失去Ribbon中的負載均衡能力,取消掉@LoadBalanced注解即可:
package cn.sher6j.springcloud.config;import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate;/*** @author sher6j* @create 2020-05-19-18:54*/ @Configuration public class ApplicationContextConfig {@Bean // @LoadBalanced//使用該注解賦予RestTemplate負載均衡的能力public RestTemplate getRestTemplate() {return new RestTemplate();} } //applicationContext.xml <bean id="" class=""> -
然后編寫自己的負載均衡接口:
package cn.sher6j.springcloud.lb;import org.springframework.cloud.client.ServiceInstance;import java.util.List;/*** 負載均衡算法發接口* @author sher6j* @create 2020-05-20-22:23*/ public interface LoadBalancer {/*** 從服務列表中用負載均衡算法選擇出具體的實例* @param serviceInstances 服務列表* @return*/ServiceInstance instances(List<ServiceInstance> serviceInstances); }給接口定義了方法instances用于在服務提供方服務的所有服務實例中選擇一個具體實例。
-
用輪詢負載均衡算法實現負載均衡接口:
package cn.sher6j.springcloud.lb.impl;import cn.sher6j.springcloud.lb.LoadBalancer; import org.springframework.cloud.client.ServiceInstance; import org.springframework.stereotype.Component;import java.util.List; import java.util.concurrent.atomic.AtomicInteger;/*** @author sher6j* @create 2020-05-20-22:26*/ @Component public class MyLB implements LoadBalancer {private AtomicInteger atomicInteger = new AtomicInteger(0);public final int getAndIncrement() {int current;int next;//自旋鎖do {current = this.atomicInteger.get(); //初始值為0next = current >= 2147483647 ? 0 : current + 1;}while (!this.atomicInteger.compareAndSet(current, next));System.out.println("========訪問次數next:" + next);return next;}/*** 從服務列表中用輪詢負載均衡算法選擇出具體的實例* Rest接口第幾次請求數 % 服務器集群總數量 = 實際調用服務器位置下標* @param serviceInstances 服務列表* @return*/@Overridepublic ServiceInstance instances(List<ServiceInstance> serviceInstances) {int index = getAndIncrement() % serviceInstances.size();return serviceInstances.get(index);} }RoundRobinRule源碼中用for(;;)實現的自旋鎖,這里我們用do{} while();實現自旋鎖。
-
最后我們在80服務的Controller中添加方法:
@GetMapping("/consumer/payment/lb")public String getPaymentLB() {//獲取服務提供方所有的服務實例List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");if (instances == null || instances.size() <= 0) {return null;}//采用自己實現的輪詢負載均衡算法選擇具體實例ServiceInstance serviceInstance = loadBalancer.instances(instances);URI uri = serviceInstance.getUri();return restTemplate.getForObject(uri + "/payment/lb", String.class);}
在瀏覽器中輸入http://localhost/consumer/payment/lb,也就是80端口的服務消費方采用我們自己編寫的輪詢負載均衡算法訪問CLOUD-PAYMENT-SERVICE服務的具體實例,測試結果如下:
在服務消費方80服務的后端控制臺也輸出了入下的日志:
總結
以上是生活随笔為你收集整理的SpringCloud[04]Ribbon负载均衡服务调用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑突然上不了网,该怎么解决
- 下一篇: 滤波电路(上),无源滤波器