開發與維運

Ribbon負載均衡與原理解析

Ribbon負載均衡服務調用

在這裡插入圖片描述

簡介

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


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

負載均衡

1.什麼是負載均衡

Load balancing,即負載均衡,是一種計算機技術,用來在多個計算機(計算機集群)、網絡連接、CPU、磁盤驅動器或其他資源中分配負載,以達到最優化資源使用、最大化吞吐率、最小化響應時間、同時避免過載的目的。
常見的負載均衡有軟件:Nginx ,LVS 硬件 F5等

2.為什麼需要負載均衡

我們在日常生活中經常免不了要去一些比較擁擠的地方,比如地鐵站、火車站、電影院、銀行等。無論是買票,還是排隊入場,這些場所一般都會設置多個服務點或者入口的。如果沒有人引導的話,大多數情況下,最近的入口會擠滿人。而哪些距離較遠的服務點或者入口就寬鬆很多。

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

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

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

在這裡插入圖片描述
在這裡插入圖片描述
Ribbon 在工作時分成兩步

第一步先選擇EurekaServer , 它優先選擇在同一區域內負載較少的Server。第二步在根據用戶指定的策略,它從server取到的服務註冊列表中選擇一個地址。其中Ribbon提供了多種策略:比如輪詢、隨機和根據響應時間加權。


在我們新版的 netflix-eureka-client 中已經集成了Ribbon
在這裡插入圖片描述

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

Ribbon核心組件IRUle

在這裡插入圖片描述
他是一個接口

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.netflix.loadbalancer;

public interface IRule {
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

Ribbon 常見的負載算法:

  • 默認為RoundRobinRule輪詢。

在這裡插入圖片描述


替換Ribbon負載算法

在這裡插入圖片描述
Ribbon的自定義配置類不可以放在@ComponentScan所掃描的當前包下以及子包下,否則這個自定義配置類就會被所有的Ribbon客戶端共享,達不到為指定的Ribbon定製配置,而@SpringBootApplication註解裡就有@ComponentScan註解,所以不可以放在主啟動類所在的包下。(因為Ribbon是客戶端(消費者)這邊的,所以Ribbon的自定義配置類是在客戶端(消費者)添加,不需要在提供者或註冊中心添加)

1.新建包Ribbon ==不能和啟動類在一個包==
在這裡插入圖片描述

package com.yxl.ribbon;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MySelfRule {

    @Bean
    public IRule myRule(){
        return new RandomRule();    //負載均衡機制改為隨機
    }
}

2.在啟動類加入註解

@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
  • name為指定的服務名(服務名必須與註冊中心顯示的服務名大小寫一致)
  • configuration為指定服務使用自定義配置(自定義負載均衡機制)

Ribbon負載均衡算法原理

  • 負載均衡算法:rest 接口第幾次請求數 % 服務器集群總數量 = 世紀調用服務器位置下標,每次服務器重啟後 rest接口計數從 1 開始
  • 如: List [0] instances = 127.0.0.1:8001

            &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;    List [1] instances = 127.0.0.1:8002
    
  • 8001 + 8002 組合成為集群,他們共計2臺機器,集群總數為2,按照輪詢算法原理 :

當請求數為1時: 1%2=1 對應下標1 則請求 8002 機器
當請求數為1時: 2%2=0 對應下標0 則請求 8001 機器
當請求數為1時: 3%2=1 對應下標1 則請求 8002 機器
當請求數為1時: 4%2=0 對應下標0 則請求 8001 機器
如此類推......

RoundRobinRule源碼
/*
 *
 * Copyright 2013 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * The most well known and basic load balancing strategy, i.e. Round Robin Rule.
 *
 * @author stonse
 * @author Nikos Michalakis <[email protected]>
 *
 */
 //繼承 AbstractLoadBalancerRule
public class RoundRobinRule extends AbstractLoadBalancerRule {

    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;

    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() {
    // 初始化AtomicInteger 原子類 0
        nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RoundRobinRule(ILoadBalancer lb) {
        this();
        setLoadBalancer(lb);
    }

    public Server choose(ILoadBalancer lb, Object key) {
    //判斷傳來的負載是否為 null
        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();
            // 如果等於 0 return
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
//取餘操作
            int nextServerIndex = incrementAndGetModulo(serverCount);
            //獲取下標
            server = allServers.get(nextServerIndex);
            //如果server ==null 設置優先級
            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;
    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

 private int incrementAndGetModulo(int modulo) {
        //CAS加自旋鎖
        //CAS(Conmpare And Swap):是用於實現多線程同步的原子指令。CAS機制當中使用了3個基本操作數:內存地址V,舊的預期值A,要修改的新值B。更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,才會將內存地址V對應的值修改為B。
        //自旋鎖:是指當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那麼該線程將循環等待,然後不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會退出循環。 
        for (;;) {
            //獲取value,即0
            int current = nextServerCyclicCounter.get();
            //取餘,為1
            int next = (current + 1) % modulo;
            //進行CAS判斷,如果此時在value的內存地址中,如果value和current相同,則為true,返回next的值,否則就一直循環,直到結果為true
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }

AtomicInteger的compareAndSet方法:

public class AtomicInteger extends Number implements java.io.Serializable {
    ...
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    ...
}

Leave a Reply

Your email address will not be published. Required fields are marked *