Nginx加權輪詢算法

輪詢調度

輪詢調度非常簡單,就是每次選擇下一個節點進行調度。比如{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個當前節點。

  1. 每個節點,用它們的當前值加上它們自己的權重。
  2. 選擇當前值最大的節點爲選中節點,並把它的當前值減去所有節點的權重總和。

例如{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
選擇分兩步

  1. 爲每個節點加上它的權重值
  2. 選擇最大的節點減去總的權重值

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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章