SpringCloud-Ribbon負載均衡機制、手寫輪詢算法


Ribbon 內置的負載均衡規則

com.netflix.loadbalancer 包下有一個接口 IRule,它可以根據特定的算法從服務列表中選取一個要訪問的服務,默認使用的是「輪詢機制」

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

負載規則的替換

如果不想使用 Ribbon 默認使用的規則,我們可以通過自定義配置類的方式,手動指定使用哪一種。

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

因此我們需要在 Spring Boot 啟動類所在包的外面新建一個包存放自定義配置類

@Configuration
public class MyselfRule {
    
    @Bean
    public IRule rule(){
        //隨機
        return new RandomRule();
    }
}

然后在啟動類上添加如下注解,指定服務名及自定義配置類

@RibbonClient(value = "CLOUD-PAYMENT-SERVICE", configuration = MyselfRule.class)

Ribbon 默認負載輪詢算法的原理

算法概述

rest 接口第幾次請求數 % 服務器集群總個數 = 實際調用服務器位置下標,服務每次重啟后 rest 請求數變為1

源碼

public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        log.warn("no load balancer");
        return null;
    }

    Server server = null;
    int count = 0;
    //循環獲取服務,最多獲取10次
    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;
}

//通過此方法獲取服務的下標,使用了 CAS 和自旋鎖
private int incrementAndGetModulo(int modulo) {
    for (;;) {
        int current = nextServerCyclicCounter.get();
        int next = (current + 1) % modulo;
        if (nextServerCyclicCounter.compareAndSet(current, next))
            return next;
    }
}

手寫輪詢算法

在服務提供者寫一個方法,返回端口號看效果就行

@GetMapping("/payment/lb")
public String roundLb(){
    return this.serverPort;
}

負載均衡接口

public interface LoadBalancer {
    /**
     * 獲取服務實例
     */
    ServiceInstance getInstance(List<ServiceInstance>serviceInstances);
}

算法實現類

@Component
public class MyLb implements LoadBalancer {

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    /**
     * 使用「自旋鎖」和「CAS」增加請求次數
     */
    public final int incrementAndGet() {
        int current;
        int next;
        do {
            current = atomicInteger.get();
            //防溢出
            next = current >= Integer.MAX_VALUE ? 0 : current + 1;
        } while (!atomicInteger.compareAndSet(current, next));
        return next;
    }

    @Override
    public ServiceInstance getInstance(List<ServiceInstance> serviceInstances) {
        // 實際調用服務器位置下標 = rest 接口第幾次請求數 % 服務器集群總個數
        int index = incrementAndGet() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}

編寫服務消費者方法,記得注釋 @LoadBalanced 注解,否則不生效

@GetMapping("/consumer/payment/lb")
public String roundLb(){
    List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    if (instances == null || instances.size() <= 0){
        return null;
    }
    ServiceInstance instance = loadBalancer.getInstance(instances);
    URI uri = instance.getUri();
    return restTemplate.getForObject(uri + "/payment/lb", String.class);
}


免責聲明!

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



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