最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring?Cloud?Ribbon的使用原理解析

 更新時間:2022年07月20日 11:32:40   作者:怪?咖@  
現(xiàn)在Java非常流行微服務,也就是所謂的面向服務開發(fā),將一個項目拆分成了多個項目,其優(yōu)點有很多,其中一個優(yōu)點就是:將服務拆分成一個一個微服務后,我們很容易的來針對性的進行集群部署,這篇文章主要介紹了Spring?Cloud?Ribbon的使用詳解,需要的朋友可以參考下

一、概述

1、Ribbon是什么

Ribbon是Netflix發(fā)布的開源項目,Spring Cloud Ribbon是基于Netflix Ribbon實現(xiàn)的一套客戶端負載均衡的框架。

2、Ribbon能干什么

LB負載均衡(Load Balance)是什么?

簡單的說就是將用戶的請求平攤的分配到多個服務上,從而達到系統(tǒng)的HA(高可用)。
常見的負載均衡有軟件Nginx,硬件 F5等。

什么情況下需要負載均衡?

現(xiàn)在Java非常流行微服務,也就是所謂的面向服務開發(fā),將一個項目拆分成了多個項目,其優(yōu)點有很多,其中一個優(yōu)點就是:將服務拆分成一個一個微服務后,我們很容易的來針對性的進行集群部署。例如訂單模塊用的人比較多,我就可以將這個模塊多部署幾臺機器,來分擔單個服務器的壓力。

這時候有個問題來了,前端頁面請求的時候到底請求集群當中的哪一臺?既然是降低單個服務器的壓力,所以肯定全部機器都要利用起來,而不是說一臺用著,其他空余著。這時候就需要用負載均衡了,像這種前端頁面調用后端請求的,要做負載均衡的話,常用的就是Nginx。

Ribbon和Nginx負載均衡區(qū)別

  • 當后端服務是集群的情況下,前端頁面調用后端請求,要做負載均衡的話,常用的就是Nginx。
  • Ribbon主要是在服務端內做負載均衡,舉例:訂單后端服務 要調用 支付后端服務,這屬于后端之間的服務調用,壓根根本不經過頁面,而支付后端服務是集群,這時候訂單服務就需要做負載均衡來調用支付服務,記住是訂單服務做負載均衡 來調用 支付服務。

負載均衡分類

  • 集中式LB:即在服務的消費方和提供方之間使用獨立的LB設施(可以是硬件,如F5, 也可以是軟件,如nginx),由該設施負責把訪問請求通過某種策略轉發(fā)至服務的提供方;
  • 進程內LB:將LB邏輯集成到消費方,消費方從服務注冊中心獲知有哪些地址可用,然后自己再從這些地址中選擇出一個合適的服務器。

Ribbon負載均衡

Ribbon就屬于進程內LB,它只是一個類庫,集成于消費方進程。

舉例:微服務經常會涉及到A服務調用B服務的接口,這時候就需要用HTTP遠程調用框架,常見的有Feign、RestTemplate、HttpClient,假如B服務只有一個節(jié)點,這時候我們可以在調用的時候寫固定ip來進行調用,假如B服務的節(jié)點存在多個(也就是集群),那A服務究竟調用B服務的哪個節(jié)點呢,這時候可以通過負載均衡框架來計算出調用哪個,比如輪詢調用B服務的多個節(jié)點,總不可能一直調用人家的一個服務,這樣B服務的集群有什么意義呢?或者也可以隨機調用任意節(jié)點,總之負載均衡的作用就是避免一直調用一個節(jié)點。

大概的流程:RestTemplate或者Feign可以通過注冊中心拿到服務提供方的IP+端口,假如提供者有多個,那他就會拿到多個地址,有了這些地址就差訪問的時候訪問哪個地址的服務了,而Ribbon可以很好的和RestTemplate或者Feign進行集成,來決定調用哪個服務,具體是負載均衡還是隨機Ribbon都可以設置。

3、Ribbon現(xiàn)狀

項目處于維護狀態(tài) ,已經一年多沒有更新過了。

https://github.com/Netflix/ribbon

4、未來替代方案

5、架構說明

首先通過上圖一定要明白一點:ribbon一定是用在消費方,而不是服務的提供方!

Ribbon在工作時分成兩步(這里以Eureka為例,consul和zk同樣道理):

  • 第一步先選擇 EurekaServer ,它優(yōu)先選擇在同一個區(qū)域內負載較少的server.
  • 第二步再根據(jù)用戶指定的策略,在從server取到的服務注冊列表中選擇一個地址。

其中Ribbon提供了多種策略:比如輪詢、隨機和根據(jù)響應時間加權。

Spring Cloud Eureka服務注冊中心入門流程分析

http://m.fzitv.net/article/204240.htm

之前寫樣例時候沒有引入spring-cloud-starter-ribbon也可以使用ribbon,這是為什么?

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

猜測spring-cloud-starter-netflix-eureka-client自帶了spring-cloud-starter-ribbon引用

證明如下: 可以看到spring-cloud-starter-netflix-eureka-client 確實引入了Ribbon(zk和consul注冊中心同樣是如此)

二、RestTemplate 用法詳解

本篇涉及到的項目均使用RestTemplate結合Ribbon來完成遠程負載均衡調用!

RestTemplate 用法詳解:http://m.fzitv.net/article/256124.htm

三、Ribbon核心組件IRule

IRule:根據(jù)特定算法中從服務列表中選取一個要訪問的服務

Ribbon給提供了很多現(xiàn)成的算法類,IRule就是最頂層的算法類接口,Ribbon默認是輪詢規(guī)則。假如我們想要修改算法,只需要將算法類注入到容器。然后通過簡單的配置就可以修改。

這些算法類都在如下包當中,一般我們只要引入Eureka、zk、consul三個其中一個注冊中心的依賴,就會附帶Ribbon的依賴,Ribbon依賴就會依賴ribbon-loadbalancer包。

  • ClientConfigEnabledRoundRobinRule:該策略較為特殊,我們一般不直接使用它。因為它本身并沒有實現(xiàn)什么特殊的處理邏輯。一般都是可以通過繼承他重寫一些自己的策略,默認的choose方法就實現(xiàn)了線性輪詢機制
  • BestAvailableRule:繼承自ClientConfigEnabledRoundRobinRule,會先過濾掉由于多次訪問故障而處于斷路器跳閘狀態(tài)的服務,然后選擇一個并發(fā)量最小的服務,該策略的特性是可選出最空閑的實例
  • PredicateBasedRule:繼承自ClientConfigEnabledRoundRobinRule,抽象策略,需要重寫方法的,然后自己來自己定義過濾規(guī)則的
  • AvailabilityFilteringRule:繼承PredicateBasedRule,先過濾掉故障實例,再選擇并發(fā)較小的實例
  • ZoneAvoidanceRule:繼承PredicateBasedRule,默認規(guī)則,復合判斷server所在區(qū)域的性能和server的可用性選擇服務器
  • com.netflix.loadbalancer.RoundRobinRule:輪詢
  • WeightedResponseTimeRule:對RoundRobinRule的擴展,響應速度越快的實例選擇權重越大,越容易被選擇
  • ResponseTimeWeightedRule:對RoundRobinRule的擴展,響應時間加權
  • com.netflix.loadbalancer.RandomRule:隨機
  • com.netflix.loadbalancer.StickyRule:這個基本也沒人用
  • com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略獲取服務,如果獲取服務失敗則在指定時間內會進行重試,獲取可用的服務
  • ZoneAvoidanceRule:默認規(guī)則,復合判斷server所在區(qū)域的性能和server的可用性選擇服務器

四、實戰(zhàn)項目

1、回顧之前的項目

http://m.fzitv.net/article/204240.htm

如下是項目當中涉及到的微服務:

ribbon一定是用在消費端,A調用B服務的接口,那么A就是消費端

在這個項目示例當中,在消費者服務當中通過RestTemplate+@LoadBalanced來完成負載均衡調用提供者。

這里調用提供者的時候不再是固定ip,而是通過服務名稱調用。相當于通過服務名稱向注冊中心當中去獲取注冊的服務,假如注冊了兩個名稱一樣的服務,那么就獲取到了兩個ip,RestTemplate內部控制了訪問哪個ip的服務。他是如何負載均衡的?就是和Ribbon無縫結合,具體原理后續(xù)再說。

注意:RestTemplate想要通過服務名稱來調用,那么一定要配置@LoadBalanced注解,不然會報錯的,只有配置了這個注解,RestTemplate才會和Ribbon相結合。

服務名稱就是在提供者的application當中配置的。

2、@RibbonClient注解用法

這個注解的意思就是,當RestTemplate調用服務名稱為CLOUD-PAYMENT-SERVICE的時候,采用MySelfRule當中注入的負載均衡算法。

@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration=MySelfRule.class)

官方文檔明確給出了警告:這個自定義配置類不能放在@ComponentScan所掃描的當前包下以及子包下,否則我們自定義的這個配置類就會被所有的Ribbon客戶端所共享,達不到特殊化定制的目的了(也就是一旦被掃描到,RestTemplate直接不管調用哪個服務都會用指定的算法)。

springboot項目當中的啟動類使用了@SpringBootApplication注解,這個注解內部就有@ComponentScan注解,默認是掃描啟動類包下所有的包,所以我們要達到定制化一定不要放在他能掃描到的地方。

cloud中文官網:https://www.springcloud.cc/spring-cloud-greenwich.html#netflix-ribbon-starter

3、配置文件用法

如下配置就可以取代@RibbonClient注解,注意一定要使用全類名,沒有@RibbonClient級別高:

CLOUD-PAYMENT-SERVICE:
  ribbon:
    NFLoadBalancerRuleClassName: com.gzl.myrule.MySelfRule
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration=MySelfRule.class)

4、修改默認算法

我們還是基于這個Eureka項目示例來進行演示修改默認算法::http://m.fzitv.net/article/204240.htm

1. 修改cloud-consumer-order80(ribbon一定是用在消費端,A調用B服務的接口,那么A就是消費端)

新建package,只要不和啟動類在同一個包下即可!

@Configuration
public class MySelfRule {
    @Bean
    public IRule myRule() {
        //定義為隨機
        return new RandomRule();
    }
}

2、主啟動類添加@RibbonClient(這個是一定要指定的,不然他不知道我們要修改算法,假如配置文件方式指定了就不需要添加這個注解了)

在啟動該微服務的時候就能去加載我們的自定義Ribbon配置類,從而使配置生效:

@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration= MySelfRule.class)

3、測試

這時候再測試訪問消費者接口,會發(fā)現(xiàn)已經不再是輪詢訪問了,成為了隨機訪問!

訪問:http://localhost/consumer/payment/get/1

五、Ribbon原理

1、負載均衡算法

以輪詢算法為例:rest接口第幾次請求數(shù) % 服務器集群總數(shù)量 = 實際調用服務器位置下標

每次服務重啟動后rest接口計數(shù)從1開始。

為什么要獲取服務器下標呢?

算法完全是基于DiscoveryClient來從注冊中心獲取到注冊的服務列表,獲取的是個List<ServiceInstance>,有了下標,有了服務list集合,那我們自然就知道要訪問哪個服務了。

import org.springframework.cloud.client.discovery.DiscoveryClient;
@Resource
private DiscoveryClient discoveryClient;

List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for (ServiceInstance element : instances) {
            System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"
                    + element.getUri());
        }

輸出的結果:

如: List [0] instances = 127.0.0.1:8002
   List [1] instances = 127.0.0.1:8001

8001+ 8002 組合成為集群,它們共計2臺機器,集群總數(shù)為2, 按照輪詢算法原理:

  • 當總請求數(shù)為1時: 1 % 2 =1 對應下標位置為1 ,則獲得服務地址為127.0.0.1:8001
  • 當總請求數(shù)位2時: 2 % 2 =0 對應下標位置為0 ,則獲得服務地址為127.0.0.1:8002
  • 當總請求數(shù)位3時: 3 % 2 =1 對應下標位置為1 ,則獲得服務地址為127.0.0.1:8001
  • 當總請求數(shù)位4時: 4 % 2 =0 對應下標位置為0 ,則獲得服務地址為127.0.0.1:8002
  • 如此類推…

2、源碼分析

我看的Cloud的Hoxton.SR1版本,版本之間源碼略有不同,但是大概思路差不多。

ribbon實現(xiàn)的關鍵點是為ribbon定制的RestTemplate,ribbon利用了RestTemplate的攔截器機制,在攔截器中實現(xiàn)ribbon的負載均衡。負載均衡的基本實現(xiàn)就是利用applicationName從服務注冊中心獲取可用的服務地址列表,然后通過一定算法負載,決定使用哪一個服務地址來進行http調用。

1.Ribbon的RestTemplate

RestTemplate中有一個屬性是List<ClientHttpRequestInterceptor> interceptors,如果interceptors里面的攔截器數(shù)據(jù)不為空,在RestTemplate進行http請求時,這個請求就會被攔截器攔截進行,攔截器需要實現(xiàn)ClientHttpRequestInterceptor接口,接口就一個方法,需要實現(xiàn)以下方法:

也就是說攔截器需要完成http請求,并封裝一個標準的response返回。

2.Ribbon中的攔截器

在Ribbon 中就是通過名字為LoadBalancerInterceptor的攔截器,注入到RestTemplate中,進行攔截請求,然后實現(xiàn)負載均衡調用的。

攔截器定義在:org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration

這個類是在這個包下,并不在Ribbon的包下:

攔截器的定義與攔截器注入器的定義:下面的bean是攔截器注入器

3.Ribbon中的攔截器注入到RestTemplate

定義了攔截器,自然需要把攔截器注入到、RestTemplate才能生效,那么Ribbon中是如何實現(xiàn)的?上面說了攔截器的定義與攔截器注入器的定義,那么肯定會有個地方使用注入器來注入攔截器的。

還是在這個類當中:org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration

遍歷context中的注入器,調用注入方法,為目標RestTemplate注入攔截器,注入器和攔截器都是我們定義好的。

還有關鍵的一點是:需要注入攔截器的目標restTemplates到底是哪一些?因為RestTemplate實例在context中可能存在多個,不可能所有的都注入攔截器,這里就是@LoadBalanced注解發(fā)揮作用的時候了。

4.LoadBalanced注解

嚴格上來說,這個注解是spring cloud實現(xiàn)的,不是ribbon中的,它的作用是在依賴注入時,只注入實例化時被@LoadBalanced修飾的實例。

例如我們定義Ribbon的RestTemplate的時候是這樣的:

@Bean
@LoadBalanced
public RestTemplate rebbionRestTemplate(){
    return new RestTemplate();
}

因此才能為我們定義的RestTemplate注入攔截器。

那么@LoadBalanced是如何實現(xiàn)這個功能的呢?其實都是spring的原生操作,@LoadBalance的源碼如下

@Qualifier注解很重要:

@Autowired默認是根據(jù)類型進行注入的,因此如果有多個類型一樣的Bean候選者,則需要限定其中一個候選者,否則將拋出異常,@Qualifier限定描述符除了能根據(jù)名字進行注入,更能進行更細粒度的控制如何選擇候選者

@LoadBalanced很明顯,‘繼承’了注解@QualifierRestTemplates通過@Autowired注入,同時被@LoadBalanced修飾,所以只會注入@LoadBalanced修飾的RestTemplate,也就是我們的目標RestTemplate。

5.攔截器邏輯實現(xiàn)

這里使用的是LoadBalancerInterceptor攔截器

當我們每通過RestTemplate調用一個接口的時候都會經過這個攔截器,通過攔截器當中的intercept方法,然后執(zhí)行excute的時候,打斷點會發(fā)現(xiàn)他會執(zhí)行到這:

在這里就是根據(jù)對應的負載均衡算法選擇對應的服務,RibbonLoadBalancerClient就是Ribbon當中的類了。由此可以看出框架有時候就是這樣,來回套用,cloud對外提供API,然后組件進行真正的實現(xiàn),假如感覺ribbon滿足不了我們,我們完全可以按照cloud的API來開發(fā)新的負載均衡框架,進行無縫替換。

(1)getLoadBalancer(serviceId):可以理解為,再第一次請求到來時,創(chuàng)建好IClientConfig(客戶端配置)、ServerList(從配置文件中加載的服務列表)、IRule(負載均衡策略)與IPing (探活策略)等Bean,是一種懶加載的模式。

(2)getServer(loadBalancer, hint):則是通過以上的負載均衡策略與探活策略,從服務列表中選擇合適的服務實例(詳細代碼在ZoneAwareLoadBalancer的chooseServer方法中)。Server對象包含ip、端口與協(xié)議等信息。

重點看getServer方法,看看是如何選擇服務的

默認就是ZoneAvoidanceRule負載均衡算法!

ZoneAvoidanceRule:繼承PredicateBasedRule,他是沒有重寫choose方法的,這時候就進入到了父類的choose方法。

public Server choose(Object key) {
   ILoadBalancer lb = getLoadBalancer();
	// 這里就完成了服務的選擇
	// 而且我們可以看到,這里的lb.getAllServers 說明ILoadBalancer直接存儲或者間接存儲了服務列表
   Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
   if (server.isPresent()) {
       return server.get();
   } else {
       return null;
   }       
}

從上面可以看到chooseRoundRobinAfterFiltering 這個方法的意思就是在過濾之后,選擇輪詢的負載均衡方式。

lb.getAllServers是獲取該服務的所有服務實例。

由此可見chooseRoundRobinAfterFiltering就是選擇的關鍵點了。

public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
    // 過濾掉不復合條件的服務實例
	List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
     if (eligible.size() == 0) {
         return Optional.absent();
     }
	// incrementAndGetModulo 這個就是輪詢的關鍵計算
     return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
 }

其計算過程還是比較簡單的,使用了AtomicInteger來計算訪問的次數(shù),cas+自旋鎖來控制多線程的安全性!

private final AtomicInteger nextIndex = new AtomicInteger();

六、手寫負載均衡器

1.RestTemplate去掉注解@LoadBalanced

2.LoadBalancer接口(在80消費者添加)

這個接口相當于是傳進去多個服務,然后根據(jù)實現(xiàn)類,來選擇出一個服務,至于是輪詢還是隨機,我們自己實現(xiàn)。

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

public interface LoadBalancer {
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

3.定義實現(xiàn)類(在80消費者添加)

@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;
    }

    // 下標和服務數(shù)進行取模
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        int index = getAndIncrement() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}

4.調整8001服務和8002服務,這兩個服務是提供者,新增一個接口,來進行測試使用!

@Value("${server.port}")
private String serverPort;

@GetMapping(value = "/payment/lb")
 public String getPaymentLB() {
     return serverPort;
 }

5.在消費者80端添加測試接口

@GetMapping("/consumer/payment/lb")
public String getPaymentLB() {
    // 這個是利用的cloud自帶的DiscoveryClient,假如cloud項目使用了注冊中心都可以通過服務名稱來獲取對應的服務信息
    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);
}

6.測試

http://localhost/consumer/payment/lb

這樣我們就成功自己實現(xiàn)了一個負載均衡!

到此這篇關于Spring Cloud Ribbon的使用詳解的文章就介紹到這了,更多相關Spring Cloud Ribbon使用內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論

麦盖提县| 宜兰县| 泰兴市| 安新县| 洞头县| 教育| 无为县| 岚皋县| 南昌市| 洛川县| 德清县| 华坪县| 鄂伦春自治旗| 云安县| 新建县| 子长县| 左云县| 南平市| 麻城市| 巴林左旗| 百色市| 美姑县| 霸州市| 青浦区| 邓州市| 新闻| 东乌珠穆沁旗| 兴义市| 和田市| 岳西县| 招远市| 新宁县| 祁连县| 政和县| 奉化市| 赣榆县| 大兴区| 出国| 边坝县| 望城县| 名山县|