怎么做網(wǎng)站結(jié)構(gòu)圖seo公司彼億營銷
一、前言
????????接下來是開展一系列的 SpringCloud 的學(xué)習(xí)之旅,從傳統(tǒng)的模塊之間調(diào)用,一步步的升級為 SpringCloud 模塊之間的調(diào)用,此篇文章為第三篇,即介紹 Ribbon 負(fù)載均衡服務(wù)調(diào)用
二、概述
2.1?Ribbon 是什么
????????Spring Cloud Ribbon 是基于 Netflix Ribbon 實(shí)現(xiàn)的一套客戶端負(fù)載均衡的工具。
????????簡單的說,Ribbon 是 Netflix 發(fā)布的開源項(xiàng)目,主要功能是提供客戶端的軟件負(fù)載均衡算法和服務(wù)調(diào)用。Ribbon 客戶端組件提供一系列完善的配置項(xiàng)如連接超時,重試等。簡單的說,就是在配置文件中列出 Load Balancer(簡稱 LB)后面所有的機(jī)器,Ribbon 會自動的幫助你基于某種規(guī)則(如簡單輪詢,隨機(jī)連接等)去連接這些機(jī)器。我們很容易使用 Ribbon 實(shí)現(xiàn)自定義的負(fù)載均衡算法。
2.2 Ribbon 資料
? ? ? ? 可以進(jìn)入官網(wǎng),查看該工具的使用方式,github 源碼的地址在這,不過到目前為止, Ribbon 也進(jìn)入了維護(hù)模式,如下圖
2.3 Ribbon 用途
????????Ribbon 可以實(shí)現(xiàn)負(fù)載均衡(Load Balance)的功能。
2.3.1?負(fù)載均衡是什么
????????簡單的說就是將用戶的請求平攤的分配到多個服務(wù)上,從而達(dá)到系統(tǒng)的 HA(高可用)。常見的負(fù)載均衡有軟件 Nginx,LVS,硬件 F5 等。
2.3.2 Ribbon 和 Nginx 區(qū)別
????????Ribbon 本地負(fù)載均衡客戶端 和 Nginx 服務(wù)端負(fù)載均衡有什么區(qū)別呢?
????????Nginx 是服務(wù)器負(fù)載均衡,客戶端所有請求都會交給 nginx,然后由 nginx 實(shí)現(xiàn)轉(zhuǎn)發(fā)請求。即負(fù)載均衡是由服務(wù)端實(shí)現(xiàn)的。
????????Ribbon 本地負(fù)載均衡,在調(diào)用微服務(wù)接口時候,會在注冊中心上獲取注冊信息服務(wù)列表之后緩存到 JVM 本地,從而在本地實(shí)現(xiàn) RPC 遠(yuǎn)程服務(wù)調(diào)用技術(shù)。
2.3.3 總結(jié)
? ? ? ? 前面第二篇文章我們通過配置訂單模塊的負(fù)載均衡,輪詢的去訪問我們的兩個支付模塊 8001 和 8002 ,采用的技術(shù)就是 負(fù)載均衡+ RestTemplate 調(diào)用。
三、Ribbon 負(fù)載均衡演示
3.1 架構(gòu)說明
????????Ribbon 在工作時分成兩步,第一步先選擇 EurekaServer,它優(yōu)先選擇在同一個區(qū)域內(nèi)負(fù)載較少的 server。第二步再根據(jù)用戶指定的策略,在從 server 取到的服務(wù)注冊列表中選擇一個地址。
????????Ribbon 提供了多種策略:比如輪詢、隨機(jī)和根據(jù)響應(yīng)時間加權(quán)。
3.2 pom 說明
? ? ? ? 之前在?cloud-consumer-order80?訂單模塊我們并沒有引入 Ribbon 的依賴也可以使用它,那是因?yàn)?strong> spring-cloud-starter-netflix-eureka-client 自帶了 spring-cloud-starter-ribbon 引用,如下圖:
3.3?RestTemplate
? ? ? ? 在沒有使用 OpenFeign 組件之前,我們還是使用 RestTemplate 來進(jìn)行遠(yuǎn)程服務(wù)的調(diào)用,其主要使用的方法就是 getForObject/getForEntity、postForObject/postForEntity 如下所示:
@RestController
@Slf4j
public class OrderController {@Resourceprivate RestTemplate restTemplate;// public static final String PaymentSrv_URL = "http://localhost:8001";public static final String PAYMENT_SRV = "http://CLOUD-PAYMENT-SERVICE";@GetMapping("/consumer/payment/create")public CommonResult create(Payment payment){// 客戶端用瀏覽器是get請求,但是底層實(shí)質(zhì)發(fā)送post調(diào)用服務(wù)端8001// return restTemplate.postForObject(PAYMENT_SRV+"/payment/create",payment,CommonResult.class);return restTemplate.postForEntity(PAYMENT_SRV+"/payment/create",payment,CommonResult.class).getBody();}// 返回對象為響應(yīng)體中數(shù)據(jù)轉(zhuǎn)化成的對象,基本上可以理解為Json@GetMapping("/consumer/payment/get/{id}")public CommonResult getPayment(@PathVariable Long id){return restTemplate.getForObject(PAYMENT_SRV + "/payment/get/"+id, CommonResult.class, id);}// 返回對象為 ResponseEntity 對象,包含了響應(yīng)中的一些重要信息,比如響應(yīng)頭、響應(yīng)狀態(tài)碼、響應(yīng)體等@GetMapping("/consumer/payment/getForEntity/{id}")public CommonResult getPayment2(@PathVariable Long id){ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_SRV + "/payment/get/" + id, CommonResult.class, id);if(entity.getStatusCode().is2xxSuccessful()){return entity.getBody();}else{return new CommonResult(444,"操作失敗");}}}
四、Ribbon 核心組件 IRule
4.1 默認(rèn)的負(fù)載均衡算法
? ? ? ? IRule 是負(fù)載均衡頂級的父接口,它會根據(jù)特定的算法從服務(wù)列表中選取一個要訪問的服務(wù),它的實(shí)現(xiàn)類,它的類圖如下所示:
? ? ? ? IRule 接口現(xiàn)有的實(shí)現(xiàn)類,即默認(rèn)存在的負(fù)載均衡策略如下:
? ? ? ? 1、com.netflix.loadbalancer.RoundRobinRule,輪詢(默認(rèn))
? ? ? ? 2、com.netflix.loadbalancer.RandomRule,隨機(jī)
? ? ? ? 3、com.netflix.loadbalancer.RetryRule,先按照 RoundRobinRule 的策略獲取服務(wù),如果獲取服務(wù)失敗則在指定時間內(nèi)會進(jìn)行重試,獲取可用的服務(wù)。
? ? ? ? 4、WeightedResponseTimeRule,對?RoundRobinRule?的擴(kuò)展,響應(yīng)速度越快的實(shí)例選擇權(quán)重越大,越容易被選擇。
? ? ? ? 5、BestAvailableRule,會先過濾掉由于多次訪問故障而處于斷路器跳閘狀態(tài)的服務(wù),然后選擇一個并發(fā)量最小的服務(wù)。
? ? ? ? 6、AvailabilityFilteringRule,先過濾掉故障實(shí)例,再選擇并發(fā)較小的實(shí)例
? ? ? ? 7、ZoneAvoidanceRule,默認(rèn)規(guī)則,復(fù)合判斷 server 所在區(qū)域的性能和 server 的可用性選擇服務(wù)器。
4.2 替換默認(rèn)負(fù)載均衡算法
4.2.1 配置細(xì)節(jié)
? ? ? ? 我們需要新建一個自定義配置類來替換默認(rèn)的負(fù)載均衡算法,但是官方文檔明確給出了警告,這個自定義配置類不能放在 @ComponentScan 所掃描的當(dāng)前包下以及子包下,否則我們自定義的這個配置類就會被所有的 Ribbon 客戶端所共享,達(dá)不到特殊化定制的目的了。
? ? ? ? 我們在?cloud-consumer-order80 模塊上進(jìn)行配置,現(xiàn)在的項(xiàng)目結(jié)構(gòu)如下所示:
? ? ? ? 我們?nèi)绻胍远x一個配置類,就需要重新創(chuàng)建一個包,且這個包不被 Spring 掃描到,如下圖:
4.2.2 創(chuàng)建配置類
? ? ? ? 在 myrule 包下,創(chuàng)建自定義配置類 MySelfRule,代碼如下所示:
@Configuration
public class MySelfRule {@Beanpublic IRule myRule(){// 定義一個隨機(jī)類型的負(fù)載均衡算法return new RandomRule();}
}
4.2.3 修改主啟動類
? ? ? ? 在主啟動類上,添加注解,并指定負(fù)載均衡的類型和服務(wù)的名稱,如下:
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration= MySelfRule.class)
public class OrderMain80 {public static void main(String[] args) {SpringApplication.run(OrderMain80.class,args);}
}
4.2.4 測試
? ? ? ? 分別啟動 cloud-eureka-server7001、cloud-eureka-server7002、cloud-provider-payment8001、cloud-provider-payment8002 和 cloud-consumer-order80 模塊,然后不斷的刷新請求地址?http://localhost/consumer/payment/get/1,如下圖:
五、Ribbon 負(fù)載均衡算法
5.1 原理
? ? ? ?輪詢負(fù)載均衡算法:rest 接口第幾次請求數(shù) % 服務(wù)器集群總數(shù)量 = 實(shí)際調(diào)用服務(wù)器位置下標(biāo) ,每次服務(wù)重啟動后 rest 接口計(jì)數(shù)從 1 開始。
? ? ? ? 以?8081 和 8082 組合成集群為例,一共兩臺機(jī)器,集群總數(shù)量為 2 ,按照輪詢的算法原理:
? ? ? ? 1、當(dāng)總請求數(shù)為 1 時: 1 % 2 =1 對應(yīng)下標(biāo)位置為 1 ,則獲得服務(wù)地址為 127.0.0.1:8001
? ? ? ? 2、當(dāng)總請求數(shù)位 2 時: 2 % 2 =0 對應(yīng)下標(biāo)位置為 0 ,則獲得服務(wù)地址為 127.0.0.1:8002
? ? ? ? 3、當(dāng)總請求數(shù)位 3 時: 3 % 2 =1 對應(yīng)下標(biāo)位置為 1 ,則獲得服務(wù)地址為 127.0.0.1:8001
? ? ? ? 4、?當(dāng)總請求數(shù)位 4 時: 4 % 2 =0 對應(yīng)下標(biāo)位置為 0 ,則獲得服務(wù)地址為 127.0.0.1:8002
? ? ? ? 以此類推。
5.2 手寫負(fù)載均衡算法
? ? ? ? 接下來我們手寫一個輪詢類型的負(fù)載均衡算法。
????????首先在?cloud-provider-payment8001?和?cloud-provider-payment8002?模塊的 PaymentController 類中添加如下的方法用于后續(xù)的測試:
@GetMapping(value = "/payment/lb")
public String getPaymentLB()
{return serverPort;
}
? ? ? ? ?然后把?cloud-consumer-order80 模塊的 ApplicationContextConfig 類的負(fù)載均衡的注解注釋掉,如下:
@Configuration
public class ApplicationContextConfig {@Bean//使用@LoadBalanced注解賦予RestTemplate負(fù)載均衡的能力// @LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}
}
? ? ? ? ?在?cloud-consumer-order80 模塊中創(chuàng)建一個接口和實(shí)現(xiàn)類,用來實(shí)現(xiàn)輪詢的負(fù)載均衡,如下:
package com.springcloud.lb;import org.springframework.cloud.client.ServiceInstance;import java.util.List;public interface LoadBalancer {// 從服務(wù)器集群中選擇一臺來處理請求ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
package com.springcloud.lb;import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;@Component
public class MyLB implements LoadBalancer{// 創(chuàng)建一個原子類來記錄服務(wù)調(diào)用的次數(shù)private AtomicInteger atomicInteger = new AtomicInteger(0);public final int getAndIncrement(){int current;int next;do{// 獲取當(dāng)前的調(diào)用次數(shù)的值current = this.atomicInteger.get();// 獲取下一次調(diào)用次數(shù)的值,若值大于 Integer 的最大值則重新開啟計(jì)數(shù)next = current > 2147483647 ? 0 : current +1;// 自旋鎖,current 為當(dāng)前值,next 為期望值,如果不滿足這個條件則一直自旋,使用 CAS 保證線程安全}while(!this.atomicInteger.compareAndSet(current,next));System.out.println("***********next 第幾次調(diào)用:"+next);return next;}@Overridepublic ServiceInstance instances(List<ServiceInstance> serviceInstances) {// 用當(dāng)前調(diào)用次數(shù)的下標(biāo)除以集群的數(shù)量,然后取余數(shù)int index = getAndIncrement() % serviceInstances.size();// 最終確定由集群的哪臺機(jī)器處理請求return serviceInstances.get(index);}
}
? ? ? ? ?修改?cloud-consumer-order80 模塊的 OrderController 類的代碼,使其調(diào)用方法時采用我們指定的負(fù)載均衡的策略,代碼如下:
@RestController
@Slf4j
public class OrderController {@Resourceprivate RestTemplate restTemplate;// public static final String PaymentSrv_URL = "http://localhost:8001";public static final String PAYMENT_SRV = "http://CLOUD-PAYMENT-SERVICE";@GetMapping("/consumer/payment/create")public CommonResult create(Payment payment){// 客戶端用瀏覽器是get請求,但是底層實(shí)質(zhì)發(fā)送post調(diào)用服務(wù)端8001return restTemplate.postForObject(PAYMENT_SRV+"/payment/create",payment,CommonResult.class);}// 返回對象為響應(yīng)體中數(shù)據(jù)轉(zhuǎn)化成的對象,基本上可以理解為Json@GetMapping("/consumer/payment/get/{id}")public CommonResult getPayment(@PathVariable Long id){return restTemplate.getForObject(PAYMENT_SRV + "/payment/get/"+id, CommonResult.class, id);}@Resourceprivate LoadBalancer loadBalancer;@Resourceprivate DiscoveryClient discoveryClient;@GetMapping("/consumer/payment/lb")public String getPaymentLB(){// 獲取集群上所有可用的節(jié)點(diǎn)List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");if(instances == null || instances.size()<=0) {return null;}// 選擇具體由哪臺機(jī)器干活ServiceInstance serviceInstance = loadBalancer.instances(instances);URI uri = serviceInstance.getUri();// 干活return restTemplate.getForObject(uri+"/payment/lb",String.class);}
}
? ? ? ? 接下來進(jìn)行測試,啟動各個模塊,在瀏覽器輸入?http://localhost/consumer/payment/lb,如下圖,可以看到刷新一次瀏覽器,界面顯示的都會發(fā)生有規(guī)則的變化。
? ? ? ? 控制臺的后臺打印內(nèi)容如下:?