目錄
- 前言
- 1. Ribbon 基礎知識
- 1.1 Ribbon 是什么
- 1.2 與 Ribbon 互動的三種級別層次
- 1.3 Ribbon在作業時分成兩步
- 1.4 服務的提供者與消費者
- 1.5 Ribbon 核心組件 IRule
- 2. 服務消費者獲取提供者的三個層次示例
- 2.1 引入 pom.xml 依賴
- 2.2 使用 Spring DiscoveryClient 查找服務實體
- 2.2.1 在主程式類上添加注解
- 2.2.2 使用 DiscoveryClient 查找資訊
- 2.3 使用帶有 Ribbon 功能的 Spring RestTemplate 呼叫服務
- 2.3.1 在主程式類上添加注解
- 2.3.2 使用 Ribbon 的 RestTemplate 來呼叫服務
- 2.4 使用 Netflix Feign 客戶端呼叫服務
- 2.4.1 在主程式類上添加注解
- 2.4.2 定義用于呼叫服務提供者的 Feign 介面
- 3. 通過 java 配置類自定義負載均衡演算法示例(消費者服務)
- 3.1 撰寫配置類
- 3.2 主啟動類上添加注解
- 4. 通過配置自定義負載均衡演算法示例(消費者服務)
- 4.1 修改 bootstrap.yml 組態檔
- 4.2 主程式類
- 5. 本地負載均衡器的實作(消費者)
- 5.1 不使用 RestTemplate
- 5.2 定義負載均衡介面
- 5.3 實作負載均衡介面
- 5.4 在 controller 介面中使用本地負載均衡器
- 最后
前言
參考資料:
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服務原理與實戰》
《B站 尚硅谷 SpringCloud 框架開發教程 周陽》
Spring Cloud Ribbon 是基于 Netflix Ribbon 實作的一套客戶端負載均衡的工具;提供客戶端的軟體負載均衡演算法和服務呼叫;
1. Ribbon 基礎知識
1.1 Ribbon 是什么
- Spring Cloud Ribbon 是基于 Netflix Ribbon 實作的一套客戶端負載均衡的工具;提供客戶端的軟體負載均衡演算法和服務呼叫;
- Ribbon 客戶端組件提供一系列完善的配置項如連接超時,重試等,簡單的說,就是在組態檔中列出 Load Balancer(簡稱LB)后面所有的機器,Ribbon 會自動基于某種規則(如簡單輪詢,隨機連接等)去連接這些機器;
- 可以很容易使用Ribbon實作自定義的負載均衡演算法;
1.2 與 Ribbon 互動的三種級別層次
- Spring DiscoveryClient:提供了對 Ribbon 和 Ribbon 中快取的注冊服務的最低層次訪問;
- 啟用了 RestTemplate 的 Spring DiscoveryClient;
- Netflix Feign 客戶端;
1.3 Ribbon在作業時分成兩步

- 第一步先選擇 EurekaServer,它優先選擇在同一個區域內負載較少的 server;
第二步再根據用戶指定的策略,在從 server 取到的服務注冊串列中選擇一個地址;
1.4 服務的提供者與消費者
- 提供者:服務提供者將自己注冊進注冊中心,讓消費者發現;在本例中有多個提供者給消費者提供服務;
- 消費者:消費者使用服務發現,找到提供者并呼叫提供者服務;在本例中只有一個消費者在多個提供者中選出一個為自己服務;
1.5 Ribbon 核心組件 IRule

- 根據特定演算法中從服務串列中選取一個要訪問的服務;
- 定義了負載均衡的方式;
- 有以下幾種負載均衡的實作方式:
- RoundRobinRule:輪詢;
- RandomRule:隨機;
- RetryRule:先按照 RoundRobinRule 的策略獲取服務,如果獲取服務失敗則在指定時間內會進行重試,獲取可用的服務;
- WeightedResponseTimeRule:對 RoundRobinRule 的擴展,回應速度越快的實體選擇權重越大,越容易被選擇;
- BestAvailableRule:會先過濾掉由于多次訪問故障而處于斷路器跳閘狀態的服務,然后選擇一個并發量最小的服務;
- AvailabilityFilteringRule:先過濾掉故障實體,再選擇并發較小的實體;
- ZoneAvoidanceRule:默認規則,復合判斷 server 所在區域的性能和 server 的可用性選擇服務器;
2. 服務消費者獲取提供者的三個層次示例
2.1 引入 pom.xml 依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
- 如果使用 Eureka 作為注冊中心,則不用引入該依賴,因為 Eureka 的依賴里包含 ribbon 相關依賴 jar 包;
2.2 使用 Spring DiscoveryClient 查找服務實體
2.2.1 在主程式類上添加注解
- @EnableDiscoveryClient:表明可以被注冊中心發現,是 Sring Cloud 的觸發器,其作用是使應用程式能夠使用 DiscoveryClient 和 Ribbon 庫;
2.2.2 使用 DiscoveryClient 查找資訊
在服務消費者的 client 包下;
@Component
public class ProviderDiscoveryClient {
//自動注入 DiscoveryClient 類,該類用于與 Ribbon 互動
@Autowired
private DiscoveryClient discoveryClient;
public Provide getProvide(String providerId) {
RestTemplate restTemplate = new RestTemplate();
//獲取服務提供者的所有實體串列,ServiceInstance 用于保存關于服務的特定實體(包括主機名、埠荷 URL)
List<ServiceInstance> instances = discoveryClient.getInstances("provider-instance-name");
if (instances.size()==0) return null;
//檢索要呼叫的服務端點
String serviceUri = String.format("%s/providers/%s",instances.get(0).getUri().toString(), providerId);
//使用標準的 Spring REST 模板類去呼叫服務
ResponseEntity< provider > restExchange =
restTemplate.exchange(
serviceUri,
HttpMethod.GET,
null, Provider.class, providerId);
return restExchange.getBody();
}
}
-
這種方法存在以下問題:
- 沒有利用 Ribbon 的客戶端負載均衡,呼叫哪個服務實體需要開發人員定義;
- 開發人員必須構建一個用來呼叫服務的 URL;
- 實體化 ResTemplate 類,不符合 Spring IoC 規范;
-
結合本篇《5. 本地負載均衡器的實作(消費者)》即可用到客戶端負載均衡,即:開發人員定義了本地負載均衡器來實作了負載均衡;
2.3 使用帶有 Ribbon 功能的 Spring RestTemplate 呼叫服務
2.3.1 在主程式類上添加注解
@SpringBootApplication //只需要這個注解即可
public class Application {
@LoadBalanced //告訴 Spring Cloud 創建一個支持 Ribbon 的 RestTemplate
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- Spring Cloud 早期版本中,RestTemplate 類默認自動支持 Ribbon;
- 自從 Spring Cloud 發布 Angel 版本后,Spring Cloud 中的 RestTemplate 不再支持 Ribbon;
- 因此,后續版本必須使用
@LoadBalanced注解顯式標注,才能將 Ribbon 和 RestTemplate 一起使用; - *RestTemplate 不一定放在主程式類里;也可以在 config 包下新建一個 ApplicationContextConfig 配置類,將 RestTemplate 放在該類里:
@Configuration
public class ApplicationContextConfig{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
2.3.2 使用 Ribbon 的 RestTemplate 來呼叫服務
在服務消費者的 client 包下;
@Component
public class ProviderRestTemplateClient {
//自動注入即可,不用實體化
@Autowired
RestTemplate restTemplate;
public Provider getProvider(String providerId){
ResponseEntity<Provider> restExchange =
restTemplate.exchange(
//使用 Eureka 服務 ID 來構建目標 URL
"http://provider-instance-name/providers/{providerId}",
HttpMethod.GET,
null, Provider.class, providerId);
return restExchange.getBody();
}
}
- 通過使用 RestTemplate 類,Ribbon 將在所有服務實體之間輪詢負載均衡所有請求;
2.4 使用 Netflix Feign 客戶端呼叫服務
Feign 相關知識將在下篇《4.2 基于 Feign 與 OpenFeign 的服務介面呼叫》中說明,這里僅把重點放在與上述兩種呼叫提供者服務的區別與對比;
2.4.1 在主程式類上添加注解
@EnableFeignClients:表示啟用 Feign 客戶端;
2.4.2 定義用于呼叫服務提供者的 Feign 介面
@FeignClient("provider-instance-name") //標識服務為 feign 的客戶端
public interface ProviderFeignClient {
//定義端點的路徑和動作
@RequestMapping(
method= RequestMethod.GET,
value="https://www.cnblogs.com/providers/{providerId}",
consumes="application/json")
//定義傳入端點的引數,該方法可以由客戶端呼叫以觸發組織服務
Provider getProvider(@PathVariable("providerId") String providerId);
}
- 要是用 ProviderFeignClient 類,開發人員只需要自動裝配并使用它即可;
3. 通過 java 配置類自定義負載均衡演算法示例(消費者服務)
指切換默認的負載均衡演算法,切換后的仍為現成的(與本地負載均衡器有所區別,本地負載均衡器要自己實作);
3.1 撰寫配置類
- 注意:自定義配置類不能放在
@ComponentScan所掃描的當前包下以及子包下,否則自定義的配置類會被所有的Ribbon客戶端所共享,達不到自定義的目的; @ComponentScan注解被封裝到主啟動類上的@SpringBootApplication注解,其默認掃描主啟動類所在包及其子包,因此我們要回傳上一級目錄新建一個 myRule 目錄存放我們自定義的負載均衡配置類;

@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();//定義為隨機
}
}
3.2 主啟動類上添加注解
- @RibbonClient(name = "provider-instance-name" ,configuration=MySelfRule.class):表示使用自定義負載均衡演算法;
name:指定服務提供者的實體名稱;configuration:指定需要使用哪個配置類的負載均衡;- 表示 provider 服務使用 MySelfRule 對應的 Ribbon 配置;
- 同樣,需要對 RestTemplate 類用 @LoadBalanced 注解顯示宣告;
4. 通過配置自定義負載均衡演算法示例(消費者服務)
指切換默認的負載均衡演算法,切換后的仍為現成的(與本地負載均衡器有所區別,本地負載均衡器要自己實作);
4.1 修改 bootstrap.yml 組態檔
- 上述 java 配置類的效果等價于下面這樣的組態檔:
#服務提供者的實體名稱
provider-instance-name:
ribbon:
#代表 Ribbon 使用的負載均衡策略,屬性的值為:IRule 的實作類
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
#其他可用的配置屬性
# NFLoadBalancerClassName : 配置 ILoadBalancer 的實作類
# NFLoadBalancerPingClassName : 配置 IPing 的實作類
# NIWSServerListClassName: 配置 ServerList 的實作類
# NIWSServerListFilterClassName: 配置 ServerListtFilter 的實作類
4.2 主程式類
- 不需要 @RibbonClient 注解;
- 同樣,需要對 RestTemplate 類用 @LoadBalanced 注解顯示宣告;
5. 本地負載均衡器的實作(消費者)
本地負載均衡器不同于自定義負載均衡演算法;前者的負載均衡演算法需要自己手動實作,后者只是切換成另一種現成的負載均衡演算法;
5.1 不使用 RestTemplate
- 即主程式類不需要對 RestTemplate 類用 @LoadBalanced 注解顯示宣告;
- 可以刪去也可以注釋 @LoadBalanced 注解;
5.2 定義負載均衡介面
可以新建一個包,專門存放我們自己寫的負載均衡演算法;
public interface LoadBalancer{
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
5.3 實作負載均衡介面
- 可以根據業務要求寫不同的負載均衡演算法,這里僅提供一種示例;
- 該示例實作了一種較為簡單的原子性的負載均衡演算法;
@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();
next = current >= 2147483647 ? 0 : current + 1;
}while(!this.atomicInteger.compareAndSet(current,next));
System.out.println("*****第幾次訪問,次數next: "+next);
return next;
}
//負載均衡演算法:rest介面第幾次請求數 % 服務器集群總數量 = 實際呼叫服務器位置下標 ,每次服務重啟動后rest介面計數從1開始,
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances){
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
5.4 在 controller 介面中使用本地負載均衡器
- 類似于本篇《2.2 使用 Spring DiscoveryClient 查找服務實體》;
- 不同之處在于 2.2 沒有負載均衡功能,這里在 2.2 的基礎上,開發人員自己定義了本地負載均衡器,不使用 Ribbon 提供的負載均衡,故《5.1 不使用 RestTemplate》中提到的不用對 RestTemplate 類使用 @LoadBalanced 注解顯示宣告;
@RestController
public class OrderController{
//服務提供者示例的名字
public static final String PAYMENT_URL = "http://provider-instance-name";
@Resource
private RestTemplate restTemplate;
@Resource
private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
@GetMapping(value = "https://www.cnblogs.com/provider/mylb")
public String getProviderLB(){
//獲取服務提供者的所有實體串列
List<ServiceInstance> instances = discoveryClient.getInstances("provider-instance-name");
if(instances == null || instances.size() <= 0){
return null;
}
//使用本地負載均衡器選出提供者服務
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"/provider/mylb",String.class);
}
}
最后

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/421660.html
標籤:架構設計
