天天看點

SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 

目錄

一、Ribbon簡介

1、是什麼

2、官網資料

3、能幹嗎

二、Ribbon負載均衡示範

1、架構說明

2、pom說明

3、二說RestTemplate的使用 

三、Ribbon核心元件IRule

1、IRule接口實作類說明

2、Ribbon負載規則替換

四、Ribbon負載均衡算法 

1、原理

2、ribbon源碼

3、手寫負載均衡器

一、Ribbon簡介

1、是什麼

Spring Cloud Ribbon是基于Netflix Ribbon實作的一套用戶端負載均衡的工具。

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

2、官網資料

https://github.com/Netflix/ribbon/wiki/Getting-Started

Ribbon目前也進入維護模式 https://github.com/Netflix/ribbon

SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 

 未來替換方案

SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 

3、能幹嗎

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

簡單的說就是将使用者的請求平攤的配置設定到多個服務上,進而達到系統的HA(高可用),常見的負載均衡有軟體Nginx,LVS,硬體 F5等

Ribbon本地負載均衡用戶端 VS Nginx服務端負載均衡差別?

  • Nginx是伺服器負載均衡,用戶端所有請求都會交給nginx,然後由nginx實作轉發請求。即負載均衡是由服務端實作的
  • Ribbon是本地負載均衡,在調用微服務接口時候,會在注冊中心上擷取注冊資訊服務清單之後緩存到JVM本地,進而在本地實作RPC遠端服務調用技術

集中式LB

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

程序内LB

将LB邏輯內建到消費方,消費方從服務注冊中心獲知有哪些位址可用,然後自己再從這些位址中選擇出一個合适的伺服器。Ribbon就屬于程序内LB,它隻是一個類庫,內建于消費方程序,消費方通過它來擷取到服務提供方的位址

一句話

負載均衡+RestTemplate調用

二、Ribbon負載均衡示範

1、架構說明

Ribbon在工作時分成兩步:

第一步先選擇 EurekaServer ,它優先選擇在同一個區域内負載較少的server

第二步再根據使用者指定的政策,從server取到的服務注冊清單中選擇一個位址;其中Ribbon提供了多種政策:比如輪詢、随機和根據響應時間權重

SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 

總結:Ribbon其實就是一個軟負載均衡的用戶端元件,他可以和其他所需請求的用戶端結合使用,和eureka結合隻是其中的一個執行個體。

2、pom說明

之前寫樣例時候沒有引入spring-cloud-starter-ribbon也可以使用ribbon

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

證明如下: 可以看到spring-cloud-starter-netflix-eureka-client 确實引入了Ribbon

SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 

3、二說RestTemplate的使用 

官網

https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 

getForObject方法/getForEntity方法

getForObject方法:傳回對象為響應體中資料轉化成的對象,基本上可以了解為Json

SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 

getForEntity方法:傳回對象為ResponseEntity對象,包含了響應中的一些重要資訊,比如響應頭、響應狀态碼、響應體 

SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 

加到 cloud-consumer-order80 的controller類裡

@GetMapping("/consumer/payment/getForEntity/{id}")
    public CommonResult<Payment> getPayment2(@PathVariable("id")Long id){
        ResponseEntity<CommonResult> eneity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
        if(eneity.getStatusCode().is2xxSuccessful()){
            log.info(eneity.getStatusCode()+"\t"+eneity.getHeaders());
            return eneity.getBody();
        }else {
            return new CommonResult<>(444,"操作失敗");
        }
    }
           

 測試

SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 

postForObject方法/postForEntity方法

SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 

GET請求方法

<T> T getForObject(String url, Class<T> responseType, Object... uriVariables);
 
<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables);
 
<T> T getForObject(URI url, Class<T> responseType);
 
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables);
 
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables);
 
<T> ResponseEntity<T> getForEntity(URI var1, Class<T> responseType);
           

POST請求方法

<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
 
<T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
 
<T> T postForObject(URI url, @Nullable Object request, Class<T> responseType);
 
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables);
 
<T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables);
 
<T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType);
           

三、Ribbon核心元件IRule

1、IRule接口實作類說明

IRule:根據特定算法中從服務清單中選取一個要通路的服務

SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 
  • com.netflix.loadbalancer.RoundRobinRule:輪詢
  • com.netflix.loadbalancer.RandomRule:随機
  • com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的政策擷取服務,如果擷取服務失敗則在指定時間内會進行重試,擷取可用的服務
  • WeightedResponseTimeRule:對RoundRobinRule的擴充,響應速度越快的執行個體選擇權重越大,越容易被選擇
  • BestAvailableRule:會先過濾掉由于多次通路故障而處于斷路器跳閘狀态的服務,然後選擇一個并發量最小的服務
  • AvailabilityFilteringRule:先過濾掉故障執行個體,再選擇并發較小的執行個體
  • ZoneAvoidanceRule:預設規則,複合判斷server所在區域的性能和server的可用性選擇伺服器

2、Ribbon負載規則替換

修改cloud-consumer-order80

注意配置細節

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

這個自定義配置類不能放在@ComponentScan所掃描的目前包下以及子包下,否則我們自定義的這個配置類就會被所有的Ribbon用戶端所共享,達不到特殊化定制的目的了。

建立package

com.atguigu.myrule

建立MySelfRule規則類

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

主啟動類添加@RibbonClient

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "cloud-payment-service",configuration = MySelfRule.class)
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}
           

四、Ribbon負載均衡算法 

1、原理

負載均衡算法: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

2、ribbon源碼

com.netflix.loadbalancer.RoundRobinRule類

public Server choose(ILoadBalancer lb, Object key) {

    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();

        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;
}
private int incrementAndGetModulo(int modulo) {
    for (;;) {
        int current = nextServerCyclicCounter.get();//擷取原子的值
        int next = (current + 1) % modulo;
        if (nextServerCyclicCounter.compareAndSet(current, next)) //CAS
            return next;
    }
}
           

3、手寫負載均衡器

80端實作輪詢

接口:從服務清單選出一台機器進行通路

public interface LoadBalancer {
    /**
     * 從服務清單選出一台機器進行通路
     * @param serviceInstances 服務清單
     * @return
     */
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
           

 實作類

@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 >= Integer.MAX_VALUE ? 0 : current+1;
        }while (!this.atomicInteger.compareAndSet(current,next));
        //atomicInteger.compareAndSet(expect, update),
        //比較except和update,如果相等傳回true,否則傳回fasle
        System.out.println("*********第"+next+"次通路");
        return next;
    }

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

消費方80controller類加上自己實作的輪詢方法

@Slf4j
@RestController
public class OrderController {

    @Resource
    private LoadBalancer loadBalancer;

    @GetMapping(value = "/consumer/payment/lb")
    public String getPayment(){
        //獲得服務清單
        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();

        log.info(" "+uri);
        return restTemplate.getForObject(uri+"/payment/lb",String.class);
    }

}
           

服務提供者8001,8002controller加上80端調用的方法

@GetMapping(value = "/payment/lb")
    public String getPayment(){
        return serverPort;
    }
           
注意:測試的時候注釋掉這兩個注釋,不然 restTemplate.getForObject不知道調用哪個服務,報錯如下,從報錯描述可知沒有合适的調用對象。 
SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 
SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法 
SpringCloud學習筆記(五) Ribbon 負載均衡服務調用一、Ribbon簡介二、Ribbon負載均衡示範三、Ribbon核心元件IRule四、Ribbon負載均衡算法