zoukankan      html  css  js  c++  java
  • 负载均衡算法WeightedRoundRobin(加权轮询)简介及算法实现

            Nginx的负载均衡默认算法是加权轮询算法,本文简单介绍算法的逻辑,并给出算法的Java实现版本。

            本文参考了Nginx的负载均衡 - 加权轮询 (Weighted Round Robin) 

            算法简介

            有三个节点{a, b, c},他们的权重分别是{a=5, b=1, c=1}。发送7次请求,a会被分配5次,b会被分配1次,c会被分配1次。

            一般的算法可能是:

            1、轮训所有节点,找到一个最大权重节点;

            2、选中的节点权重-1;

            3、直到减到0,恢复该节点原始权重,继续轮询;

            这样的算法看起来简单,最终效果是:{a, a, a, a, a, b, c},即前5次可能选中的都是a,这可能造成权重大的服务器造成过大压力的同时,小权重服务器还很闲。

            Nginx的加权轮询算法将保持选择的平滑性,希望达到的效果可能是{a, b, a, a, c, a, a},即尽可能均匀的分摊节点,节点分配不再是连续的。

            Nginx加权轮询算法

            1、概念解释,每个节点有三个权重变量,分别是:

            (1) weight: 约定权重,即在配置文件或初始化时约定好的每个节点的权重。

            (2) effectiveWeight: 有效权重,初始化为weight。

             在通讯过程中发现节点异常,则-1;

             之后再次选取本节点,调用成功一次则+1,直达恢复到weight;

             此变量的作用主要是节点异常,降低其权重。

            (3) currentWeight: 节点当前权重,初始化为0。

            2、算法逻辑

            (1) 轮询所有节点,计算当前状态下所有节点的effectiveWeight之和totalWeight;

            (2) currentWeight = currentWeight + effectiveWeight;  选出所有节点中currentWeight中最大的一个节点作为选中节点;

            (3) 选中节点的currentWeight = currentWeight - totalWeight;

            基于以上算法,我们看一个例子:

            这时有三个节点{a, b, c},权重分别是{a=4, b=2, c=1},共7次请求,初始currentWeight值为{0, 0, 0},每次分配后的结果如下: 

    请求序号 请求前currentWeight值 选中节点 请求后currentWeight值
    1 {c=1,b=2,a=4} a {c=1,b=2,a=-3}
    2 {c=2,b=4,a=1} b {c=2,b=-3,a=1}
    3 {c=3,b=-1,a=5} a {c=3,b=-1,a=-2}
    4 {c=4,b=1,a=2} c {c=-3,b=1,a=2}
    5 {c=-2,b=3,a=6} a {c=-2,b=3,a=-1}
    6 {c=-1,b=5,a=3} b {c=-1,b=-2,a=3}
    7 {c=0,b=0,a=7} a {c=0,b=0,a=0}

            观察到七次调用选中的节点顺序为{a, b, a, c, a, b, a},a节点选中4次,b节点选中2次,c节点选中1次,算法保持了currentWeight值从初始值{c=0,b=0,a=0}到7次调用后又回到{c=0,b=0,a=0}。

     

            算法实现

            下面附上笔者自己的Java版算法实现:

      1 package com.example.demo.arithmetic;
      2 
      3 import java.util.ArrayList;
      4 import java.util.HashMap;
      5 import java.util.List;
      6 import java.util.Map;
      7 
      8 /**
      9  * Created by caojun on 2018/2/20.
     10  *
     11  * 基本概念:
     12  * weight: 配置文件中指定的该后端的权重,这个值是固定不变的。
     13  * effective_weight: 后端的有效权重,初始值为weight。
     14  * 在释放后端时,如果发现和后端的通信过程中发生了错误,就减小effective_weight。
     15  * 此后有新的请求过来时,在选取后端的过程中,再逐步增加effective_weight,最终又恢复到weight。
     16  * 之所以增加这个字段,是为了当后端发生错误时,降低其权重。
     17  * current_weight:
     18  * 后端目前的权重,一开始为0,之后会动态调整。那么是怎么个动态调整呢?
     19  * 每次选取后端时,会遍历集群中所有后端,对于每个后端,让它的current_weight增加它的effective_weight,
     20  * 同时累加所有后端的effective_weight,保存为total。
     21  * 如果该后端的current_weight是最大的,就选定这个后端,然后把它的current_weight减去total。
     22  * 如果该后端没有被选定,那么current_weight不用减小。
     23  *
     24  * 算法逻辑:
     25  * 1. 对于每个请求,遍历集群中的所有可用后端,对于每个后端peer执行:
     26  *     peer->current_weight += peer->effecitve_weight。
     27  *     同时累加所有peer的effective_weight,保存为total。
     28  * 2. 从集群中选出current_weight最大的peer,作为本次选定的后端。
     29  * 3. 对于本次选定的后端,执行:peer->current_weight -= total。
     30  *
     31  */
     32 public class RoundRobinByWeightLoadBalance {
     33 
     34     //约定的invoker和权重的键值对
     35     final private List<Node> nodes;
     36 
     37     public RoundRobinByWeightLoadBalance(Map<Invoker, Integer> invokersWeight){
     38         if (invokersWeight != null && !invokersWeight.isEmpty()) {
     39             nodes = new ArrayList<>(invokersWeight.size());
     40             invokersWeight.forEach((invoker, weight)->nodes.add(new Node(invoker, weight)));
     41         }else
     42             nodes = null;
     43     }
     44 
     45     /**
     46      * 算法逻辑:
     47      * 1. 对于每个请求,遍历集群中的所有可用后端,对于每个后端peer执行:
     48      *     peer->current_weight += peer->effecitve_weight。
     49      *     同时累加所有peer的effective_weight,保存为total。
     50      * 2. 从集群中选出current_weight最大的peer,作为本次选定的后端。
     51      * 3. 对于本次选定的后端,执行:peer->current_weight -= total。
     52      *
     53      * @Return ivoker
     54      */
     55     public Invoker select(){
     56         if (! checkNodes())
     57             return null;
     58         else if (nodes.size() == 1) {
     59             if (nodes.get(0).invoker.isAvalable())
     60                 return nodes.get(0).invoker;
     61             else
     62                 return null;
     63         }
     64         Integer total = 0;
     65         Node nodeOfMaxWeight = null;
     66         for (Node node : nodes) {
     67             total += node.effectiveWeight;
     68             node.currentWeight += node.effectiveWeight;
     69 
     70             if (nodeOfMaxWeight == null) {
     71                 nodeOfMaxWeight = node;
     72             }else{
     73                 nodeOfMaxWeight = nodeOfMaxWeight.compareTo(node) > 0 ? nodeOfMaxWeight : node;
     74             }
     75         }
     76 
     77         nodeOfMaxWeight.currentWeight -= total;
     78         return nodeOfMaxWeight.invoker;
     79     }
     80 
     81     public void onInvokeSuccess(Invoker invoker){
     82         if (checkNodes()){
     83             nodes.stream()
     84                     .filter((Node node)->invoker.id().equals(node.invoker.id()))
     85                     .findFirst()
     86                     .get()
     87                     .onInvokeSuccess();
     88         }
     89     }
     90 
     91     public void onInvokeFail(Invoker invoker){
     92         if (checkNodes()){
     93             nodes.stream()
     94                     .filter((Node node)->invoker.id().equals(node.invoker.id()))
     95                     .findFirst()
     96                     .get()
     97                     .onInvokeFail();
     98         }
     99     }
    100 
    101     private boolean checkNodes(){
    102         return (nodes != null && nodes.size() > 0);
    103     }
    104 
    105     public void printCurrenctWeightBeforeSelect(){
    106         if (checkNodes()) {
    107             final StringBuffer out = new StringBuffer("{");
    108             nodes.forEach(node->out.append(node.invoker.id())
    109                     .append("=")
    110                     .append(node.currentWeight+node.effectiveWeight)
    111                     .append(","));
    112             out.append("}");
    113             System.out.print(out);
    114         }
    115     }
    116 
    117     public void printCurrenctWeight(){
    118         if (checkNodes()) {
    119             final StringBuffer out = new StringBuffer("{");
    120             nodes.forEach(node->out.append(node.invoker.id())
    121                     .append("=")
    122                     .append(node.currentWeight)
    123                     .append(","));
    124             out.append("}");
    125             System.out.print(out);
    126         }
    127     }
    128 
    129     public interface Invoker{
    130         Boolean isAvalable();
    131         String id();
    132     }
    133 
    134     private static class Node implements Comparable<Node>{
    135         final Invoker invoker;
    136         final Integer weight;
    137         Integer effectiveWeight;
    138         Integer currentWeight;
    139 
    140         Node(Invoker invoker, Integer weight){
    141             this.invoker = invoker;
    142             this.weight = weight;
    143             this.effectiveWeight = weight;
    144             this.currentWeight = 0;
    145         }
    146 
    147         @Override
    148         public int compareTo(Node o) {
    149             return currentWeight > o.currentWeight ? 1 : (currentWeight.equals(o.currentWeight) ? 0 : -1);
    150         }
    151 
    152         public void onInvokeSuccess(){
    153             if (effectiveWeight < this.weight)
    154                 effectiveWeight++;
    155         }
    156 
    157         public void onInvokeFail(){
    158             effectiveWeight--;
    159         }
    160     }
    161 
    162     public static void main(String[] args){
    163         Map<Invoker, Integer> invokersWeight = new HashMap<>(3);
    164         Integer aWeight = 4;
    165         Integer bWeight = 2;
    166         Integer cWeight = 1;
    167 
    168         invokersWeight.put(new Invoker() {
    169             @Override
    170             public Boolean isAvalable() {
    171                 return true;
    172             }
    173             @Override
    174             public String id() {
    175                 return "a";
    176             }
    177         }, aWeight);
    178 
    179         invokersWeight.put(new Invoker() {
    180             @Override
    181             public Boolean isAvalable() {
    182                 return true;
    183             }
    184             @Override
    185             public String id() {
    186                 return "b";
    187             }
    188         }, bWeight);
    189 
    190         invokersWeight.put(new Invoker() {
    191             @Override
    192             public Boolean isAvalable() {
    193                 return true;
    194             }
    195             @Override
    196             public String id() {
    197                 return "c";
    198             }
    199         }, cWeight);
    200 
    201         Integer times = 7;
    202         RoundRobinByWeightLoadBalance roundRobin = new RoundRobinByWeightLoadBalance(invokersWeight);
    203         for(int i=1; i<=times; i++){
    204             System.out.print(new StringBuffer(i+"").append("    "));
    205             roundRobin.printCurrenctWeightBeforeSelect();
    206             Invoker invoker = roundRobin.select();
    207             System.out.print(new StringBuffer("    ").append(invoker.id()).append("    "));
    208             roundRobin.printCurrenctWeight();
    209             System.out.println();
    210         }
    211     }
    212 }

            

     

             

     

     

  • 相关阅读:
    HVIE、HDFS常用操作
    Xshell中使用小键盘问题
    配置SSH免密登录及常见问题
    Linux命令行常用光标控制快捷键
    Linux禁止root用户ssh登录
    sqoop 1.4.7 单机安装
    Hive单机服务的安装配置
    Hadoop 2.9单机安装配置
    CentOS连接wifi
    Servlet
  • 原文地址:https://www.cnblogs.com/markcd/p/8456870.html
Copyright © 2011-2022 走看看