SpringCloud之Ribbon負載均衡


此文章很大部分轉載https://www.mrhelloworld.com/,博主均測試通過

 

什么是 Ribbon

  Ribbon 是一個基於 HTTP 和 TCP 的 客服端負載均衡工具,它是基於 Netflix Ribbon 實現的。

  它不像 Spring Cloud 服務注冊中心、配置中心、API 網關那樣獨立部署,但是它幾乎存在於每個 Spring Cloud 微服務中。包括 Feign 提供的聲明式服務調用也是基於該 Ribbon 實現的。

  Ribbon 默認提供很多種負載均衡算法,例如輪詢、隨機等等。甚至包含自定義的負載均衡算法。

Ribbon 解決了什么問題

  Ribbon 提供了一套微服務的負載均衡解決方案。

負載均衡不同方案的區別

  目前業界主流的負載均衡方案可分成兩類:

  • 集中式負載均衡(服務器負載均衡),即在 consumer 和 provider 之間使用獨立的負載均衡設施(可以是硬件,如 F5,也可以是軟件,如 nginx),由該設施負責把訪問請求通過某種策略轉發至 provider;
  • 進程內負載均衡(客戶端負載均衡),將負載均衡邏輯集成到 consumer,consumer 從服務注冊中心獲知有哪些地址可用,然后自己再從這些地址中選擇出一個合適的 provider。Ribbon 屬於后者,它只是一個類庫,集成於 consumer 進程,consumer 通過它來獲取 provider 的地址。

 

 

 

Ribbon 負載均衡策略

  策略對應類名:RoundRobinRule

  實現原理:輪詢策略表示每次都順序取下一個 provider,比如一共有 5 個 provider,第 1 次取第 1 個,第 2 次取第 2 個,第 3 次取第 3 個,以此類推。

  策略對應類名:WeightedResponseTimeRule

  實現原理:

  • 根據每個 provider 的響應時間分配一個權重,響應時間越長,權重越小,被選中的可能性越低。
  • 原理:一開始為輪詢策略,並開啟一個計時器,每 30 秒收集一次每個 provider 的平均響應時間,當信息足夠時,給每個 provider 附上一個權重,並按權重隨機選擇 provider,高權越重的 provider 會被高概率選中。

  策略對應類名:RandomRule

  實現原理:從 provider 列表中隨機選擇一個。

  策略對應類名:BestAvailableRule

  實現原理:選擇正在請求中的並發數最小的 provider,除非這個 provider 在熔斷中。

  策略對應類名:RetryRule

  實現原理:其實就是輪詢策略的增強版,輪詢策略服務不可用時不做處理,重試策略服務不可用時會重新嘗試集群中的其他節點。

  策略對應類名:AvailabilityFilteringRule

  實現原理:過濾性能差的 provider

  • 第一種:過濾掉在 Eureka 中處於一直連接失敗的 provider。
  • 第二種:過濾掉高並發(繁忙)的 provider。

  策略對應類名:ZoneAvoidanceRule

  實現原理:

  • 以一個區域為單位考察可用性,對於不可用的區域整個丟棄,從剩下區域中選可用的 provider。
  • 如果這個 ip 區域內有一個或多個實例不可達或響應變慢,都會降低該 ip 區域內其他 ip 被選中的權 重。

  eureka-demo 聚合工程。SpringBoot 2.2.4.RELEASESpring Cloud Hoxton.SR1

  Ribbon 中對於集群的服務采用的負載均衡策略默認是輪詢。

使用學習 Eureka 時的 eureka-demo 項目,在該項目中創建子項目 service-provider02

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>service-provider02</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 繼承父依賴 -->
    <parent>
        <groupId>com.example</groupId>
        <artifactId>eureka-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!-- 項目依賴 -->
    <dependencies>
        <!-- netflix eureka client 依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- spring boot web 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- lombok 依賴 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- spring boot actuator 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- spring boot test 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
  
</project>
server:
  port: 7071 # 端口

spring:
  application:
    name: service-provider # 應用名稱(集群下相同)

# 配置 Eureka Server 注冊中心
eureka:
  instance:
    prefer-ip-address: true       # 是否使用 ip 地址注冊
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  # 設置服務注冊中心地址
      defaultZone: http://root:123456@localhost:8761/eureka/,http://root:123456@localhost:8762/eureka/

# 度量指標監控與健康檢查
management:
  endpoints:
    web:
      exposure:
        include: shutdown         # 開啟 shutdown 端點訪問
  endpoint:
    shutdown:
      enabled: true               # 開啟 shutdown 實現優雅停服

將所有代碼復制粘貼一份至 server-provider02,修改啟動類名稱即可.為了更直觀的看到負載均衡的效果,我們在 service-consumer 項目中將服務地址打印至控制台。

運行完整的 Eureka 環境,訪問:http://localhost:8761/ 可以看到現在已經有兩個服務提供者。

 

 

 

多次訪問:http://localhost:9090/order/1 可以看到默認使用的是輪詢策略。

 

 

 

Ribbon 負載均衡策略設置

 

 

 這里是ribbon負載均衡默認的實現, 由於是筆記的關系,這里不好測試,只能你們自己去測試一下了, 具體怎么 使用呢?

在啟動類或配置類中注入負載均衡策略對象。所有服務請求均使用該策略。

@Bean
public IRule iRule(){
return new RoundRobinRule();
}

多次訪問:http://localhost:9090/order/1 結果如下:

修改配置文件指定服務的負載均衡策略。格式:服務應用名.ribbon.NFLoadBalancerRuleClassName

# 負載均衡策略
# service-provider 為調用的服務的名稱
service-provider:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

自定義負載均衡策略:

/*
 *
 * 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 <nikos@netflix.com>
 *
 */
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() {
        nextServerCyclicCounter = new AtomicInteger(0);
    }

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

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

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

    /**
     * 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) {
    }
}

我們來看看這個類AbstractLoadBalancerRule

/**
 * Class that provides a default implementation for setting and getting load balancer
 * @author stonse
 *
 */
public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {

    private ILoadBalancer lb;
        
    @Override
    public void setLoadBalancer(ILoadBalancer lb){
        this.lb = lb;
    }
    
    @Override
    public ILoadBalancer getLoadBalancer(){
        return lb;
    }      
}

這里我們能發現,還是我們上面所說過的 實現了IRule就能夠自定義負載均衡即使是他默認的策略也實現了IRule 我們可以直接把代碼copy過來改動一點

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.Random;

public class LubanRule extends AbstractLoadBalancerRule {
    //原來是純隨機策略 我們現在改為。 如果一個下標已經被隨機到了2次了,第三次還是同樣的下標的話,那就再隨機一次
    Random rand;
    private int lastIndex = -1;
    private int nowIndex = -1;
    private int skipIndex = -1;
    public LubanRule() {
        rand = new Random();
    }
    /**
     * Randomly choose from all living servers
     */
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;
        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers();
            List<Server> allList = lb.getAllServers();
            int serverCount = allList.size();
            if (serverCount == 0) {
                /*
                 * No servers. End regardless of pass, because subsequent passes
                 * only get more restrictive.
                 */
                return null;
            }
            int index = rand.nextInt(serverCount);
            System.out.println("當前下標為:"+index);
            if (skipIndex>=0&&index == skipIndex) {
                System.out.println("跳過");
                index = rand.nextInt(serverCount);
                System.out.println("跳過后的下標:"+index);
            }
            skipIndex=-1;
            nowIndex = index;
            if (nowIndex == lastIndex) {
                System.out.println("下一次需要跳過的下標"+nowIndex);
                skipIndex = nowIndex;
            }
            lastIndex = nowIndex;
            server = upList.get(index);
            if (server == null) {
                /*
                 * The only time this should happen is if the server list were
                 * somehow trimmed. This is a transient condition. Retry after
                 * yielding.
                 */
                Thread.yield();
                continue;
            }
            if (server.isAlive()) {
                return (server);
            }
            // Shouldn't actually happen.. but must be transient or a bug.
            server = null;
            Thread.yield();
        }
        return server;
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

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

這里我們就把自己寫的Rule給new出來交給spring 就好了

@Bean
public IRule iRule(){
   return new LubanRule();
}

具體測試的話就不測試了, 那個效果放在筆記上不太明顯,可以自己把代碼copy過去測試一下

Ribbon 點對點直連

點對點直連是指繞過注冊中心,直接連接服務提供者獲取服務,一般在測試階段使用比較多。

在調用方 pom 文件中引入 ribbon 依賴,需要注意的是如果 pom 中有 Eureka 的依賴,則需要去除 Eureka 的依賴。

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

配置文件中關閉 Eureka,添加直連的服務地址。如果不設置負載均衡策略默認使用輪詢策略。

# 負載均衡策略
# service-provider 為調用的服務的名稱
service-provider:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    # 點對點直連模式,指定具體的 Provider 服務列表,多個用逗號隔開
    listOfServers: http://localhost:7070,http://localhost:7071

# 關閉 Eureka 實現 Ribbon 點對點直連
ribbon:
  eureka:
    enabled: false # false:關閉,true:開啟

關閉 Eureka 注冊中心,服務提供者由於無法連接至注冊中心所以會報連接異常。但是服務是可以正常可消費的,所以目前使用的是點對點的方式來進行調用的。

多次訪問:http://localhost:9090/order/1 結果如下:

 

 

 至此 Ribbon 負載均衡所有的知識點就講解結束了。

 


免責聲明!

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



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