springCloud的負載均衡


 

一.什么是負載均衡

  負載均衡(Load-balance LB),指的是將用戶的請求平攤分配到各個服務器上,從而達到系統的高可用。常見的負載均衡軟件有Nginx、lvs等。

 

二.負載均衡的簡單分類

  1)集中式LB:集中式負載均衡指的是,在服務消費者(client)和服務提供者(provider)之間提供負載均衡設施,通過該設施把消費者(client)的請求通過某種策略轉發給服務提供者(provider),常見的集中式負載均衡是Nginx;

  2)進程式LB:將負載均衡的邏輯集成到消費者(client)身上,即消費者從服務注冊中心獲取服務列表,獲知有哪些地址可用,再從這些地址里選出合適的服務器,springCloud的Ribbon就是一個進程式的負載均衡工具。

三.為什么需要做負載均衡

  1) 不做負載均衡,可能導致某台機子負荷太重而掛掉;

  2)導致資源浪費,比如某些機子收到太多的請求,肯定會導致某些機子收到很少請求甚至收不到請求,這樣會浪費系統資源。

 

四.springCloud如何開啟負載均衡

  1)在消費者子工程的pom.xml文件的加入相關依賴(https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon/1.4.7.RELEASE);

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>

   消費者需要獲取服務注冊中心的注冊列表信息,把Eureka的依賴包也放進pom.xml

 <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-eureka-server</artifactId>
         <version>1.4.7.RELEASE</version>
  </dependency>

 

  2)在application.yml里配置服務注冊中心的信息

  在該消費者(client)的application.yml里配置Eureka的信息,至於如何啟動一個springCloud項目,請看這篇博客https://www.cnblogs.com/fengrongriup/p/14464208.html

#配置Eureka
eureka:
  client:
    #是否注冊自己到服務注冊中心,消費者不用提供服務
    register-with-eureka: false
    service-url:
      #訪問的url
      defaultZone: http://localhost:8002/eureka/

 

  3)在消費者啟動類上面加上注解@EnableEurekaClient

@EnableEurekaClient

  

  4)在配置文件的Bean上加上

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

 

五.IRule

 什么是IRule

  IRule接口代表負載均衡的策略,它的不同的實現類代表不同的策略,它的四種實現類和它的關系如下()

 

說明一下(idea找Irule的方法:ctrl+n   填入IRule進行查找)

1.RandomRule:表示隨機策略,它將從服務清單中隨機選擇一個服務;

public class RandomRule extends AbstractLoadBalancerRule {
    public RandomRule() {
    }

    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    //傳入一個負載均衡器
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            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) {
                    return null;
                }
                //獲取一個隨機數
                int index = this.chooseRandomInt(serverCount);
                //通過這個隨機數從列表里獲取服務
                server = (Server)upList.get(index);
                if (server == null) {
                    //當前線程轉為就緒狀態,讓出cpu
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

                    server = null;
                    Thread.yield();
                }
            }

            return server;
        }
    }

  小結:通過獲取到的所有服務的數量,以這個數量為標准獲取一個(0,服務數量)的數作為獲取服務實例的下標,從而獲取到服務實例

 

2.ClientConfigEnabledRoundRobinRule:ClientConfigEnabledRoundRobinRule並沒有實現什么特殊的處理邏輯,但是他的子類可以實現一些高級策略, 當一些本身的策略無法實現某些需求的時候,它也可以做為父類幫助實現某些策略,一般情況下我們都不會使用它;

public class ClientConfigEnabledRoundRobinRule extends AbstractLoadBalancerRule {
    //使用“4”中的RoundRobinRule策略
    RoundRobinRule roundRobinRule = new RoundRobinRule();

    public ClientConfigEnabledRoundRobinRule() {
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
        this.roundRobinRule = new RoundRobinRule();
    }

    public void setLoadBalancer(ILoadBalancer lb) {
        super.setLoadBalancer(lb);
        this.roundRobinRule.setLoadBalancer(lb);
    }

    public Server choose(Object key) {
        if (this.roundRobinRule != null) {
            return this.roundRobinRule.choose(key);
        } else {
            throw new IllegalArgumentException("This class has not been initialized with the RoundRobinRule class");
        }
    }
}

  小結:用來作為父類,子類通過實現它來實現一些高級負載均衡策略

 

1)ClientConfigEnabledRoundRobinRule的子類BestAvailableRule:從該策略的名字就可以知道,bestAvailable的意思是最好獲取的,該策略的作用是獲取到最空閑的服務實例;

public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule {
    //注入負載均衡器,它可以選擇服務實例
    private LoadBalancerStats loadBalancerStats;

    public BestAvailableRule() {
    }

    public Server choose(Object key) {
        //假如負載均衡器實例為空,采用它父類的負載均衡機制,也就是輪詢機制,因為它的父類采用的就是輪詢機制
        if (this.loadBalancerStats == null) {
            return super.choose(key);
        } else {
            //獲取所有服務實例並放入列表里
            List<Server> serverList = this.getLoadBalancer().getAllServers();
            //並發量
            int minimalConcurrentConnections = 2147483647;
            long currentTime = System.currentTimeMillis();
            Server chosen = null;
            Iterator var7 = serverList.iterator();
            //遍歷服務列表
            while(var7.hasNext()) {
                Server server = (Server)var7.next();
                ServerStats serverStats = this.loadBalancerStats.getSingleServerStat(server);
                //淘汰掉已經負載的服務實例
                if (!serverStats.isCircuitBreakerTripped(currentTime)) {
                    //獲得當前服務的請求量(並發量)
                    int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
                    //找出並發了最小的服務
                    if (concurrentConnections < minimalConcurrentConnections) {
                        minimalConcurrentConnections = concurrentConnections;
                        chosen = server;
                    }
                }
            }

            if (chosen == null) {
                return super.choose(key);
            } else {
                return chosen;
            }
        }
    }

    public void setLoadBalancer(ILoadBalancer lb) {
        super.setLoadBalancer(lb);
        if (lb instanceof AbstractLoadBalancer) {
            this.loadBalancerStats = ((AbstractLoadBalancer)lb).getLoadBalancerStats();
        }

    }
}

   小結:ClientConfigEnabledRoundRobinRule子類之一,獲取到並發了最少的服務

 

2)ClientConfigEnabledRoundRobinRule的另一個子類是PredicateBasedRule:通過源碼可以看出它是一個抽象類,它的抽象方法getPredicate()返回一個AbstractServerPredicate的實例,然后它的choose方法調用AbstractServerPredicate類的chooseRoundRobinAfterFiltering方法獲取具體的Server實例並返回

public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
    public PredicateBasedRule() {
    }
    //獲取AbstractServerPredicate對象
    public abstract AbstractServerPredicate getPredicate();

    public Server choose(Object key) {
        //獲取當前策略的負載均衡器
        ILoadBalancer lb = this.getLoadBalancer();
        //通過AbstractServerPredicate的子類過濾掉一部分實例(它實現了Predicate)
        //以輪詢的方式從過濾后的服務里選擇一個服務
        Optional<Server> server = this.getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        return server.isPresent() ? (Server)server.get() : null;
    }
}

  再看看它的chooseRoundRobinAfterFiltering()方法是如何實現的

public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
        List<Server> eligible = this.getEligibleServers(servers, loadBalancerKey);
        return eligible.size() == 0 ? Optional.absent() : Optional.of(eligible.get(this.incrementAndGetModulo(eligible.size())));
    }

  是這樣的,先通過this.getEligibleServers(servers, loadBalancerKey)方法獲取一部分實例,然后判斷這部分實例是否為空,如果不為空則調用eligible.get(this.incrementAndGetModulo(eligible.size())方法從這部分實例里獲取一個服務,點進this.getEligibleServers看

public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
        if (loadBalancerKey == null) {
            return ImmutableList.copyOf(Iterables.filter(servers, this.getServerOnlyPredicate()));
        } else {
            List<Server> results = Lists.newArrayList();
            Iterator var4 = servers.iterator();

            while(var4.hasNext()) {
                Server server = (Server)var4.next();
                //條件滿足
                if (this.apply(new PredicateKey(loadBalancerKey, server))) {
                    //添加到集合里
                    results.add(server);
                }
            }

            return results;
        }
    }

  getEligibleServers方法是根據this.apply(new PredicateKey(loadBalancerKey, server))進行過濾的,如果滿足,就添加到返回的集合中。符合什么條件才可以進行過濾呢?可以發現,apply是用this調用的,this指的是AbstractServerPredicate(它的類對象),但是,該類是個抽象類,該實例是不存在的,需要子類去實現,它的子類在這里暫時不是看了,以后有空再深入學習下,它的子類如下,實現哪個子類,就用什么 方式過濾。

   再回到chooseRoundRobinAfterFiltering()方法,剛剛說完它通過 getEligibleServers方法過濾並獲取到一部分實例,然后再通過this.incrementAndGetModulo(eligible.size())方法從這部分實例里選擇一個實例返回,該方法的意思是直接返回下一個整數(索引值),通過該索引值從返回的實例列表中取得Server實例。

private int incrementAndGetModulo(int modulo) {
        //當前下標
        int current;
        //下一個下標
        int next;
        do {
            //獲得當前下標值
            current = this.nextIndex.get();
            next = (current + 1) % modulo;
        } while(!this.nextIndex.compareAndSet(current, next) || current >= modulo);

        return current;
    }

  源碼擼明白了,再來理一下chooseRoundRobinAfterFiltering()的思路:先通過getEligibleServers()方法獲得一部分服務實例,再從這部分服務實例里拿到當前服務實例的下一個服務對象使用。

  小結:通過AbstractServerPredicate的chooseRoundRobinAfterFiltering方法進行過濾,獲取備選的服務實例清單,然后用線性輪詢選擇一個實例,是一個抽象類,過濾策略在AbstractServerPredicate的子類中具體實現

 

3.RetryRule:是對選定的負載均衡策略加上重試機制,即在一個配置好的時間段內(默認500ms),當選擇實例不成功,則一直嘗試使用subRule的方式選擇一個可用的實例,在調用時間到達閥值的時候還沒找到可用服務,則返回空,如果沒有配置負載策略,默認輪詢(即“4”中的輪詢);

  先貼上它的源碼

public class RetryRule extends AbstractLoadBalancerRule {
    //從這可以看出,默認使用輪詢機制
    IRule subRule = new RoundRobinRule();
    //閥值
    long maxRetryMillis = 500L;
    //無參構造函數
    public RetryRule() {
    }
    //使用輪詢機制
    public RetryRule(IRule subRule) {
        this.subRule = (IRule)(subRule != null ? subRule : new RoundRobinRule());
    }

    public RetryRule(IRule subRule, long maxRetryMillis) {
        this.subRule = (IRule)(subRule != null ? subRule : new RoundRobinRule());
        this.maxRetryMillis = maxRetryMillis > 0L ? maxRetryMillis : 500L;
    }
    
    public void setRule(IRule subRule) {
        this.subRule = (IRule)(subRule != null ? subRule : new RoundRobinRule());
    }

    public IRule getRule() {
        return this.subRule;
    }
    //設置最大耗時時間(閥值),最多重試多久
    public void setMaxRetryMillis(long maxRetryMillis) {
        if (maxRetryMillis > 0L) {
            this.maxRetryMillis = maxRetryMillis;
        } else {
            this.maxRetryMillis = 500L;
        }

    }
    //獲取重試的時間
    public long getMaxRetryMillis() {
        return this.maxRetryMillis;
    }
    //設置負載均衡器,用以獲取服務
    public void setLoadBalancer(ILoadBalancer lb) {
        super.setLoadBalancer(lb);
        this.subRule.setLoadBalancer(lb);
    }
    //通過負載均衡器選擇服務
    public Server choose(ILoadBalancer lb, Object key) {
        long requestTime = System.currentTimeMillis();
        //當前時間+閥值 = 截止時間
        long deadline = requestTime + this.maxRetryMillis;
        Server answer = null;
        answer = this.subRule.choose(key);
        //獲取到服務直接返回
        if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline) {
            InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());
            //獲取不到服務的情況下反復獲取
            while(!Thread.interrupted()) {
                answer = this.subRule.choose(key);
                if (answer != null && answer.isAlive() || System.currentTimeMillis() >= deadline) {
                    break;
                }

                Thread.yield();
            }

            task.cancel();
        }

        return answer != null && answer.isAlive() ? answer : null;
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

 

  小結:采用RoundRobinRule的選擇機制,進行反復嘗試,當花費時間超過設置的閾值maxRetryMills時,就返回null

 

4.RoundRobinRule:輪詢策略,它會從服務清單中按照輪詢的方式依次選擇每個服務實例,它的工作原理是:直接獲取下一個可用實例,如果超過十次沒有獲取到可用的服務實例,則返回空且報出異常信息;

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() {
        this.nextServerCyclicCounter = new AtomicInteger(0);
    }

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

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

            while(true) {
                //選擇十次,十次都沒選到可用服務就返回空
                if (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) {
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        server = (Server)allServers.get(nextServerIndex);
                        if (server == null) {
                            Thread.yield();
                        } else {
                            if (server.isAlive() && server.isReadyToServe()) {
                                return server;
                            }

                            server = null;
                        }
                        continue;
                    }

                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }

                if (count >= 10) {
                    
                    log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                }

                return server;
            }
        }
    }
    
    //遞增的形式實現輪詢
    private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
            current = this.nextServerCyclicCounter.get();
            next = (current + 1) % modulo;
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));

        return next;
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

  小結:采用線性輪詢機制循環依次選擇每個服務實例,直到選擇到一個不為空的服務實例或循環次數達到10次   

 

它有個子類WeightedResponseTimeRule,WeightedResponseTimeRule是對RoundRobinRule的優化。WeightedResponseTimeRule在其父類的基礎上,增加了定時任務這個功能,通過啟動一個定時任務來計算每個服務的權重,然后遍歷服務列表選擇服務實例,從而達到更加優秀的分配效果。我們這里把這個類分為三部分:定時任務,計算權值,選擇服務

1)定時任務

//定時任務
void initialize(ILoadBalancer lb) {
        if (this.serverWeightTimer != null) {
            this.serverWeightTimer.cancel();
        }

        this.serverWeightTimer = new Timer("NFLoadBalancer-serverWeightTimer-" + this.name, true);
       //開啟一個任務,每30秒執行一次
        this.serverWeightTimer.schedule(new WeightedResponseTimeRule.DynamicServerWeightTask(), 0L, (long)this.serverWeightTaskTimerInterval);
        WeightedResponseTimeRule.ServerWeight sw = new WeightedResponseTimeRule.ServerWeight();
        sw.maintainWeights();
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            public void run() {
                WeightedResponseTimeRule.logger.info("Stopping NFLoadBalancer-serverWeightTimer-" + WeightedResponseTimeRule.this.name);
                WeightedResponseTimeRule.this.serverWeightTimer.cancel();
            }
        }));
    }

DynamicServerWeightTask()任務如下:

class DynamicServerWeightTask extends TimerTask {
        DynamicServerWeightTask() {
        }

        public void run() {
            WeightedResponseTimeRule.ServerWeight serverWeight = WeightedResponseTimeRule.this.new ServerWeight();

            try {
                //計算權重
                serverWeight.maintainWeights();
            } catch (Exception var3) {
                WeightedResponseTimeRule.logger.error("Error running DynamicServerWeightTask for {}", WeightedResponseTimeRule.this.name, var3);
            }

        }
    }

   小結:調用initialize方法開啟定時任務,再在任務里計算服務的權重

 

2)計算權重:第一步,先算出所有實例的響應時間;第二步,再根據所有實例響應時間,算出每個實例的權重

//用來存儲權重
private volatile List<Double> accumulatedWeights = new ArrayList();

//內部類
class ServerWeight {
        ServerWeight() {
        }
        //該方法用於計算權重
        public void maintainWeights() {
            //獲取負載均衡器
            ILoadBalancer lb = WeightedResponseTimeRule.this.getLoadBalancer();
            if (lb != null) {
                if (WeightedResponseTimeRule.this.serverWeightAssignmentInProgress.compareAndSet(false, true)) {
                    try {
                        WeightedResponseTimeRule.logger.info("Weight adjusting job started");
                        AbstractLoadBalancer nlb = (AbstractLoadBalancer)lb;
                        //獲得每個服務實例的信息
                        LoadBalancerStats stats = nlb.getLoadBalancerStats();
                        if (stats != null) {
                            //實例的響應時間
                            double totalResponseTime = 0.0D;

                            ServerStats ss;
                            //累加所有實例的響應時間
                            for(Iterator var6 = nlb.getAllServers().iterator(); var6.hasNext(); totalResponseTime += ss.getResponseTimeAvg()) {
                                Server server = (Server)var6.next();
                                ss = stats.getSingleServerStat(server);
                            }

                            Double weightSoFar = 0.0D;
                            List<Double> finalWeights = new ArrayList();
                            Iterator var20 = nlb.getAllServers().iterator();
                            //計算負載均衡器所有服務的權重,公式是weightSoFar = weightSoFar + weight-實例平均響應時間
                            while(var20.hasNext()) {
                                Server serverx = (Server)var20.next();
                                ServerStats ssx = stats.getSingleServerStat(serverx);
                                double weight = totalResponseTime - ssx.getResponseTimeAvg();
                                weightSoFar = weightSoFar + weight;
                                finalWeights.add(weightSoFar);
                            }

                            WeightedResponseTimeRule.this.setWeights(finalWeights);
                            return;
                        }
                    } catch (Exception var16) {
                        WeightedResponseTimeRule.logger.error("Error calculating server weights", var16);
                        return;
                    } finally {
                        WeightedResponseTimeRule.this.serverWeightAssignmentInProgress.set(false);
                    }

                }
            }
        }
    }

 

3)選擇服務

@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;

            while(server == null) {
                List<Double> currentWeights = this.accumulatedWeights;
                if (Thread.interrupted()) {
                    return null;
                }

                List<Server> allList = lb.getAllServers();
                int serverCount = allList.size();
                if (serverCount == 0) {
                    return null;
                }

                int serverIndex = 0;
              
                double maxTotalWeight = currentWeights.size() == 0 ? 0.0D : (Double)currentWeights.get(currentWeights.size() - 1);
                if (maxTotalWeight >= 0.001D && serverCount == currentWeights.size()) {
                    //生產0到最大權重值的隨機數
                    double randomWeight = this.random.nextDouble() * maxTotalWeight;
                    int n = 0;
                    //循環權重區間
                    for(Iterator var13 = currentWeights.iterator(); var13.hasNext(); ++n) {
                        //獲取到循環的數
                        Double d = (Double)var13.next();
                        //假如隨機數在這個區間內,就拿該索引d服務列表獲取對應的實例
                        if (d >= randomWeight) {
                            serverIndex = n;
                            break;
                        }
                    }

                    server = (Server)allList.get(serverIndex);
                } else {
                    server = super.choose(this.getLoadBalancer(), key);
                    if (server == null) {
                        return server;
                    }
                }

                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

                    server = null;
                }
            }

            return server;
        }
    }

  小結:首先生成了一個[0,最大權重值) 區間內的隨機數,然后遍歷權重列表,假如當前隨機數在這個區間內,就通過該下標獲得對應的服務。

 


免責聲明!

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



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