【微服務】- SpringCloud中Ribbon的使用及其輪詢調度詳解


1.簡介

Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端負載均衡的工具。

簡單的說,Ribbon是Netflix發布的開源項目,主要功能是提供客戶端的軟件負載均衡算法和服務調用。Ribbon客戶端組件提供一系列完善的配置項如連接超時,重試等。簡單的說,就是在配置文件中列出Load Balancer (簡稱LB)后面所有的機器,Ribbon會自動的幫助你基於某種規則如簡單輪詢,隨機連接等)去連接這些機器。我們很容易使用Ribbon實現自定義的負載均衡算法。

ribbon已經不再維護,但是因為其自身足夠優秀導致ribbon現在依然被大規模使用。

ribbon未來的替代方案

圖片

2.負載均衡的介紹

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

簡單的說就是將用戶的請求平攤的分配到多個服務上,從而達到系統的HA (高可用)。

常見的負載均衡有軟件Nginx, LVS, 硬件F5等。

Ribbon本地負載均衡客戶端VS Nginx服務端負載均衡區別

Nginx是服務器負載均衡,客戶端所有請求都會交給nginx,然后由nginx實現轉發請求。即負載均衡是由服務端實現的。

Ribbon本地負載均衡,在調用微服務接口時候,會在注冊中心上獲取注冊信息服務列表之后緩存到JVM本地,從而在本地實現RPC遠程服務調用技術。

集中式LB

即在服務的消費方和提供方之間使用獨立的LB設施(可以是硬件,如F5, 也可以是軟件,如nginx), 由該設施負責把訪問請求通過某種策略轉發至服務的提供方;

進程內LB

將LB邏輯集成到消費方,消費方從服務注冊中心獲知有哪些地址可用,然后自己再從這些地址中選擇出一個合適的服務器。

Ribbon就屬於進程內LB,它只是一個類庫, 集成於消費方進程,消費方通過它來獲取到服務提供方的地址。

總結

Ribbon就是負載均衡+ RestTemplate調用

3.SpringCloud集成Ribbon

架構說明

Ribbon其實就是一 一個軟負載均衡的客戶端組件,它可以和其他所需請求的客戶端結合使用,和eureka結合只是其中的一個實例。

圖片

Ribbon在I作時分成兩步

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

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

如何引入Ribbon

我們使用的是Eureka注冊中心集群來演示Ribbon的使用,因為在引入Eureka時

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

圖片

也可以自動引入

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

圖片

故在pom中不自動引入。

RestTemplate簡介

getForObject方法/getForEntity方法

返回對象為響應體中數據轉化成的對象,基本上間以理解為Json

@GetMapping("/consumer/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
    log.info("order執行查詢");
    return template.getForObject(path + "/payment/get/"+id,CommonResult.class);
}

返回對象為ResponseEntity對象,包含了響應中的一些重要信息,比如響應頭、響應狀態碼、響應體等

@GetMapping("/consumer/payment2/get/{id}")
public CommonResult getPaymentById2(@PathVariable("id") Long id){
    log.info("order執行查詢");
    ResponseEntity<CommonResult> entity = template.getForEntity(path + "/payment/get/" + id, CommonResult.class);
    System.out.println(entity);
    if (entity.getStatusCode().is2xxSuccessful()){
        return entity.getBody();
    }else {
        return new CommonResult(444,"操作失敗");
    }
}

postForObject方法/postForEntity方法兩方法區別與get相同。

4.Ribbon核心組件IRule

作用:根據特定算法從服務中選擇一個要訪問的服務

圖片

IRule的幾個重要實現類

  • com.netflix.loadbalancer.RoundRobinRule : 輪詢
  • com.netflix.loadbalancer.RandomRule: 隨機
  • com.netflix.loadbalancer .RetryRule: 先按照RoundRobinRule的策略獲取服務,如果獲取服務失敗則在指定時間內會進行重試,獲取可用的服務
  • WeightedResponseTimeRule:對RoundRobinRule的擴展,響應速度越快的實例選擇權重越大,越容易被選擇
  • BestAvailableRule:會先過濾掉由於多次訪問故障而處於斷路器跳閘狀態的服務,然后選擇一個並發量最小的服務
  • AvailabilityFilteringRule:先過濾掉故障實例,再選擇並發較小的實例
  • ZoneAvoidanceRule:默認規則,復合判斷server所在區域的性能和server的可用性選擇服務器

自定義自己的負載均衡算法

官方文檔明確給出了警告:

這個自定義配置類不能放在@ComponentScan所掃描的當前包下以及子包下,

否則我們自定義的這個配置類就會被所有的Ribbon客戶端所共享,達不到特殊化定制的目的了

即不要把自己的負載均衡類寫在啟動類所在目錄下

自定義負載均衡類及使用

寫負載均衡相關類

@Configuration
public class MyselfRule {

    @Bean
    public IRule getRule(){
        return new RandomRule();
    }
}

在啟動類中聲明要使用的負載均衡類

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyselfRule.class)
public class OrderMain80 {

    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

就完成了
name = "CLOUD-PAYMENT-SERVICE"是要訪問的集群

configuration = MyselfRule.class自己寫的負載均衡類。

5.Ribbon負載均衡算法

負載均衡輪詢算法的原理

負載均衡算法: rest接口第幾次請求數%服務器集群總數量=實際調用服務器位置下標,每次服務重啟動后rest接口計數從1開始

List instances = discoveryClient.getInstances(“CLOUD- PAYMENT-SERVICE”);

如:

List [0] instances = 127.0.0.1:8002

List [1] instances = 127.0.0.1:8001

8001+ 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

當總請求數位4時: 4 % 2 =0對應下標位置為0,則獲得服務地址為127.0.0.1:8002

如此類推…

Ribbon輪詢算法源碼

RoundRobinRule.java

/*選擇要使用的算法*/
public Server choose(Object key) {
    return choose(getLoadBalancer(), key);
}
/*ILoadBalancer 是一個抽象類,可以通過該接口中的方法獲取所有已近注冊的服務*/
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) {
        //獲取所有可用服務
        List<Server> reachableServers = lb.getReachableServers();
        //獲取所有服務
        List<Server> allServers = lb.getAllServers();
        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;
        }
        //獲取要輪詢到的sever
        int nextServerIndex = incrementAndGetModulo(serverCount);
        server = allServers.get(nextServerIndex);

        //判斷server是否可用
        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;
}
//這個方法通過自旋和CAS來保證線程安全
private int incrementAndGetModulo(int modulo) {
    for (;;) {
        int current = nextServerCyclicCounter.get();
        //通過取余來獲取要輪詢到的server的下標
        int next = (current + 1) % modulo;
        if (nextServerCyclicCounter.compareAndSet(current, next))
            return next;
    }
}

手寫一個負載均衡算法

因為負載均衡的是要均衡的是被調用服務的集群,所以自己寫的負載均衡類寫在調用服務的服務中。

先寫一個接口,該接口有一個方法來獲取服務

public interface LoadbanenceI {

    public ServiceInstance instance(List<ServiceInstance> instances);
}

用我們自己的負載均衡類繼承上敘的接口

//加注解說明要受到spring的管理
@Component
public class MyLb implements LoadbanenceI {

    private AtomicInteger integer = new AtomicInteger(0);

    //通過CAS和輪詢的算法來獲取要調用服務的下標
    public final int getAndIncrement(){
        int cur;
        int next;
        do {
            cur = integer.get();
            next = cur >= 2147473647?0:cur+1;
        }while (!integer.compareAndSet(cur,next));
        System.out.println("----第" + cur + "次訪問----");
        return next;
    }

    @Override
    public ServiceInstance instance(List<ServiceInstance> instances) {
        int index = getAndIncrement() % instances.size();
        return instances.get(index);
    }
}

因為要所以自己寫的負載均衡算法,所以關掉之前的負載均衡

@Configuration
public class ApplicationConfig {

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

在啟動類中標明要被均衡的服務,及自己寫的負載均衡類。

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MyselfRule.class)
public class OrderMain80 {

    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

啟動所以服務后,如果調用服務集群的服務可用運行,且達到預期的均衡結果,說明自己寫的負載均衡類使用成功。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM