負載均衡之輪詢策略
輪詢演算法是最簡單的一種負載均衡演算法,它的原理是將使用者的請求輪流分配給內部的伺服器,並且輪詢演算法並不需要記錄當前所有連線的狀態,所以它是一種無狀態的排程.
簡單輪詢策略
下面是簡單輪詢演算法的實現
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},該演算法分配請求過程如下
//排序運算元,讓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)