1. 程式人生 > >負載均衡之輪詢策略

負載均衡之輪詢策略

輪詢演算法是最簡單的一種負載均衡演算法,它的原理是將使用者的請求輪流分配給內部的伺服器,並且輪詢演算法並不需要記錄當前所有連線的狀態,所以它是一種無狀態的排程.

簡單輪詢策略

下面是簡單輪詢演算法的實現

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)