负载均衡之轮询策略

版权声明:小伙伴们如有错误,欢迎交流 https://blog.csdn.net/JavaMoo/article/details/79617840

轮询算法是最简单的一种负载均衡算法,它的原理是将用户的请求轮流分配给内部的服务器,并且轮询算法并不需要记录当前所有连接的状态,所以它是一种无状态的调度.

简单轮询策略

下面是简单轮询算法的实现

public class RouteRound implements LoadBalance {
    private int count = 0;

    @Override
    public Worker route(String jobId, List<Worker> workers) {
        int index = count++ % workers.size();
        return workers.get(index);
    }
}

简单轮询算法假设所有的服务器性能都相同,在生产环境下如果所有的服务机器的性能都一样,那么这种算法没有问题,如果服务器性能不同,那么会造成服务器负载不均衡,而基于服务器性能权重的负载均衡算法可以很好的解决这个问题

权重轮询加权算法

此种算法的思路是对于一组性能权值为{a:2,b:4,c:4}的服务器列表,在十次请求的情况下,a要接收两次请求,b要接收4次请求,c要接收4四次请求

加权轮询算法解决方案如下
1. 在服务器数组S中,首先计算所有服务器权重的最大值max(S),以及所有服务器权重的最大公约数gcd(S)。
2. index表示本次请求到来时,选择的服务器的索引,初始值为-1;current_weight表示当前调度的权值,初始值为max(S)。
3. 当请求到来时,从index+1开始轮询服务器数组S,找到其中权重大于current_weight的第一个服务器,用于处理该请求。记录其索引到结果序列中。
4. 在轮询服务器数组时,如果到达了数组末尾,则重新从头开始搜索,并且减小current_weight的值:current_weight -= gcd(S)。如果current_weight等于0,则将其重置为max(S)。

/**
 * 获取worker权值列表最大公约数
 *
 * @return
 */
private int getMaxGcd(List<Worker> servers) {
    int gcd = servers.get(0).getWeight();
    for (int i = 1; i < servers.size(); i++) {
        gcd = getGcd(gcd, servers.get(i).getWeight());
    }
    return gcd;
}
 /**
  * 使用辗转相除法获取两个数的最大公约数
  *
  * @param a
  * @param b
  * @return
  */
 private int getGcd(int a, int b){
     int c;
     while (b > 0) {
         c = b;
         b = a % b;
         a = c;
     }
     return a;
 }
/**
 * 获取worker列表的最大权重
 *
 * @param servers
 * @return
 */
private int getMaxWeight(List<Worker> servers) {
    int max = servers.get(0).getWeight();
    for (int i = 1; i < servers.size(); i++) {
        if (max < servers.get(i).getWeight())
            max = servers.get(i).getWeight();
    }
    return max;
}
public Worker route(String jobId, List<Worker> workers) {
    /**
     * 在第一次调用的情况下,我们需要初始化如下三个参数
     * 1.最大公约数
     * 2.最大服务器性能权值
     * 3. 当前权值
     */
    if (curWeight < 0) {
        maxGcd = getMaxGcd(workers);
        maxWeight = getMaxWeight(workers);
        curWeight = maxWeight;
    }
    while (true) {
        for (; curIndex + 1 < workers.size(); ) {
            curIndex += 1;
            if (workers.get(curIndex).getWeight() >= curWeight) {
                return workers.get(curIndex);
            }
        }
        curWeight -= maxGcd;
        curIndex = -1;
        if (curWeight <= 0) {
            curWeight = maxWeight;
        }
    }
}

我们利用上面实现的算法验证下是否达到了我们的需求,对于服务器列表{a,b,c}权值分别为{5,1,1},7次请求调用服务器a,b,c分别应该承受5,1,1次请求

public static void main(String[] args) {
    WeightedRouteRound strategy = new WeightedRouteRound();
    List<Worker> workers = LoadBalanceUtil.getWorkers();
    System.out.println("show workers >>>");
    workers.stream().forEach(System.out::println);
    System.out.println("route >>>");
    for (int i = 0; i < 7; i++) {
        Worker worker = strategy.route("jobId", workers);
        System.out.println(worker);
    }
}

结果如下

show workers >>>
Worker(ip=a, weight=5)
Worker(ip=b, weight=1)
Worker(ip=c, weight=1)
route >>>
Worker(ip=a, weight=5)
Worker(ip=a, weight=5)
Worker(ip=a, weight=5)
Worker(ip=a, weight=5)
Worker(ip=a, weight=5)
Worker(ip=b, weight=1)
Worker(ip=c, weight=1)

复合我的预期

平滑轮询算法

加权轮询算法仍然不完美,它存在着在某一个时间点请求集中的落在权重较高的机器上,我们需要的负载均衡算法有平滑的分配请求的功能,比如对于上述的{a,a,a,a,a,b,c}负载我们希望的结果是{a,a,b,a,c,a,a}

算法如下
对于每个服务器有两个权重,一个是服务器性能权重weight,一个是服务器当前权重current_weight(初始值为0)

当请求到来的时候进行如下两步
1. 每次当请求到来,选取服务器时,会遍历数组中所有服务器。对于每个服务器,它的currentWeight=currentWeight+Weight;同时累加所有服务器的weight,并保存为total
2. 遍历完所有服务器之后,如果该服务器的current_weight是最大的,就选择这个服务器处理本次请求。最后把该服务器的current_weight减去total

比如对于服务器权重{4,2,1},该算法分配请求过程如下
image

//排序算子,让worker列表按当前权值的从大到小排序
private Comparator<WrapedWorker> cmp = (o1, o2) -> o2.currentWeight - o1.currentWeight;

private LinkedList<WrapedWorker> list = new LinkedList<>();

private boolean first = true;

@AllArgsConstructor
private class WrapedWorker {
    Worker worker;
    int currentWeight;
    public int getWeight() {
        return worker.getWeight();
    }
}
@Override
public Worker route(String jobId, List<Worker> workers) {
    /**
     * 如果是第一次调用该算法,创建对象WrapedWorker列表
     */
    if (first) {
        for (Worker worker : workers) {
            list.add(new WrapedWorker(worker, 0));
        }
        first = false;
        /**
         * 创建完所有的WrapedWorker后进行排序
         */
        list.sort(cmp);
    }
    int count = list.size();
    int total = 0;
    /**
     * 遍历所有的元素,用元素当前权重=元素的权重+元素的当前权重
     * 同时计算元素权重之和
     */
    while (--count >= 0) {
        WrapedWorker wrapedWorker = list.pollFirst();
        wrapedWorker.currentWeight = wrapedWorker.getWeight() + wrapedWorker.currentWeight;
        list.add(wrapedWorker);
        total += wrapedWorker.currentWeight;
    }
    /**
     * 排序选出最大的临时权重
     */
    list.sort(cmp);
    WrapedWorker worker = list.pollFirst();
    /**
     * 最大的临时权重减去所有权重
     */
    worker.currentWeight = worker.currentWeight - total;
    list.add(worker);
    return worker.worker;
}

验证算法的可行性

public static void main(String[] args) {
    SmoothWeightedRouteRound strategy = new SmoothWeightedRouteRound();
    List<Worker> workers = LoadBalanceUtil.getWorkers();
    for (int i = 0; i < 7; i++) {
        System.out.println(strategy.route("jobId", workers));
    }
}

结果如下

Worker(ip=a, weight=5)
Worker(ip=a, weight=5)
Worker(ip=b, weight=1)
Worker(ip=a, weight=5)
Worker(ip=c, weight=1)
Worker(ip=a, weight=5)
Worker(ip=a, weight=5)

没有更多推荐了,返回首页