輪詢調度
輪詢調度非常簡單,就是每次選擇下一個節點進行調度。比如{a, b, c}
三個節點,第一次選擇a, 第二次選擇b,第三次選擇c,接下來又從頭開始。
這樣的算法有一個問題,在負載均衡中,每臺機器的性能是不一樣的,對於16核的機器跟4核的機器, 使用一樣的調度次數,這樣對於16核的機器的負載就會很低。這時,就引出了基於權重的輪詢算法。
基於權重的輪詢調度是在基本的輪詢調度上,給每個節點加上權重,這樣對於權重大的節點, 其被調度的次數會更多。比如a, b, c三臺機器的負載能力分別是4:2:1,則可以給它們分配的權限爲4, 2, 1。 這樣輪詢完一次後,a被調用4次,b被調用2次,c被調用1次。
對於普通的基於權重的輪詢算法,可能會產生以下的調度順序{a, a, a, a, b, b, c}
。
這樣的調度順序其實並不友好,它會一下子把大壓力壓到同一臺機器上,這樣會產生一個機器一下子很忙的情況。 於是乎,就有了平滑的基於權重的輪詢算法。
所謂平滑就是調度不會集中壓在同一臺權重比較高的機器上。這樣對所有機器都更加公平。 比如,對於{a:5, b:1, c:1}
,產生{a, a, b, a, c, a, a}
的調度序列就比{c, b, a, a, a, a, a}
更加平滑。
nginx平滑的基於權重輪詢算法
nginx平滑的基於權重輪詢算法其實很簡單。算法原文 描述爲:
Algorithm is as follows: on each peer selection we increase current_weight of each eligible peer by its weight, select peer with greatest current_weight and reduce its current_weight by total number of weight points distributed among peers.
算法執行2步,選擇出1個當前節點。
- 每個節點,用它們的當前值加上它們自己的權重。
- 選擇當前值最大的節點爲選中節點,並把它的當前值減去所有節點的權重總和。
例如{a:5, b:1, c:1}
三個節點。一開始我們初始化三個節點的當前值爲{0, 0, 0}
。 選擇過程如下表:
輪數 | 選擇前的當前權重 | 選擇節點 | 選擇後的當前權重 |
---|---|---|---|
1 | {5, 1, 1} | a | {-2, 1, 1} |
2 | {3, 2, 2} | a | {-4, 2, 2} |
3 | {1, 3, 3} | b | {1, -4, 3} |
4 | {6, -3, 4} | a | {-1, -3, 4} |
5 | {4, -2, 5} | c | {4, -2, -2} |
6 | {9, -1, -1} | a | {2, -1, -1} |
7 | {7, 0, 0} | a | {0, 0, 0} |
我們可以發現,a, b, c選擇的次數符合5:1:1,而且權重大的不會被連接選擇。7輪選擇後, 當前值又回到{0, 0, 0},以上操作可以一直循環,一樣符合平滑和基於權重。
一個go版本實現
type Node struct {
Name string
Current int
Weight int
}
func SmoothWrr(nodes []*Node) (best *Node) {
if len(nodes) == 0 {
return
}
total := 0
for _, node := range nodes {
if node == nil {
continue
}
total += node.Weight
node.Current += node.Weight
if best == nil || node.Current > best.Current {
best = node
}
}
if best == nil {
return
}
best.Current -= total
return
}
func example() {
nodes := []*Node{
&Node{"a", 0, 5},
&Node{"b", 0, 1},
&Node{"c", 0, 1},
}
for i := 0; i < 7; i++ {
best := SmoothWrr(nodes)
if best != nil {
fmt.Println(best.Name)
}
}
}
一個Java的版本實現
public static Node smoothWeightedRoundRobin(final int totalWeight, final Node[] nodes) {
if (Objects.isNull(nodes) || nodes.length == 0) {
return null;
}
Node best = null;
for (Node node : nodes) {
if (Objects.isNull(node)) continue;
node.currentWeight += node.effectiveWeight;
if (Objects.isNull(best) || node.currentWeight > best.currentWeight) {
best = node;
}
}
if (Objects.nonNull(best)) {
best.currentWeight -= totalWeight;
}
return best;
}
public static class Node {
String name;
int currentWeight;
int effectiveWeight;
public Node(String name, int weight) {
this.name = name;
this.currentWeight = 0;
this.effectiveWeight = weight;
}
}
public static void main(String[] args) {
Node[] nodes = new Node[]{
new Node("a", 6),
new Node("b", 3),
new Node("c", 1),
};
int totalWeight = 0;
for (Node node : nodes) {
totalWeight += node.effectiveWeight;
}
Node[] roundRobinNodes = new Node[totalWeight];
for (int i = 0; i < totalWeight; i++) {
roundRobinNodes[i] = smoothWeightedRoundRobin(totalWeight, nodes);
}
for (Node node : roundRobinNodes) {
System.out.println("Node{" +
"name='" + node.name + '\'' +
", currentWeight=" + node.currentWeight +
", effectiveWeight=" + node.effectiveWeight +
'}');
}
}
證明權重合理性
以下證明主要由安大神證明得出
假如有n個結點,記第i個結點的權重是xi。 設總權重爲S=x1 + x2 + … + xn
選擇分兩步
- 爲每個節點加上它的權重值
- 選擇最大的節點減去總的權重值
n個節點的初始化值爲[0, 0, …, 0],數組長度爲n,值都爲0。
第一輪選擇的第1步執行後,數組的值爲[x1, x2, …, xn]。
假設第1步後,最大的節點爲j,則第j個節點減去S。
所以第2步的數組爲[x1, x2, …, xj-S, …, xn]。 執行完第2步後,數組的和爲
x1 + x2 + … + xj-S + … + xn =>
x1 + x2 + … + xn - S = S - S = 0。
由此可見,每輪選擇,第1步操作都是數組的總和加上S,第2步總和再減去S,所以每輪選擇完後的數組總和都爲0.
假設總共執行S輪選擇,記第i個結點選擇mi次。第i個結點的當前權重爲wi。 假設節點j在第t輪(t < S)之前,已經被選擇了xj次,記此時第j個結點的當前權重爲wj=t*xj-xj*S=(t-S)*xj<0, 因爲t恆小於S,所以wj<0。
前面假設總共執行S輪選擇,則剩下S-t輪,上面的公式wj=(t-S)*xj+(S-t)*xj=0。 所以在剩下的選擇中,wj永遠小於等於0,由於上面已經證明任何一輪選擇後, 數組總和都爲0,則必定存在一個節點k使得wk>0,永遠不會再選中xj。
由此可以得出,第i個結點最多被選中xi次,即mi<=xi。 因爲S=m1+m2+…+mn且S=x1 + x2 + … + xn。 所以,可以得出mi==xi。
證明平滑性
證明平滑性,只要證明不要一直都是連續選擇那一個節點即可。
跟上面一樣,假設總權重爲S,假如某個節點xi連續選擇了t(t<xi)次,只要存在下一次選擇的不是xi,即可證明是平滑的。
假設t=xi-1,此是第i個結點的當前權重爲wi=t*xi-t*S=(xi-1)*xi-(xi-1)*S。
證明下一輪的第1步執行完的值wi+xi不是最大的即可。
wi+xi=>
(xi-1)*xi-(xi-1)*S+xi=>
xi2-xi*S+S=>
(xi-1)*(xi-S)+xi
因爲xi恆小於S,所以xi-S<=-1。 所以上面:
(xi-1)*(xi-S)+xi <= (xi-1)*-1+xi = -xi+1+xi=1。
所以,第t輪後,再執行完第1步的值wi+xi<=1。
如果這t輪剛好是最開始的t輪,則必定存在另一個結點j的值爲xj*t,所以有wi+xi<=1<1*t<xj*t。
所以下一輪肯定不會選中x。