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}
三个节点,里面的{5,1,1}
分别代表abc节点的权重。一开始我们初始化三个节点的当前值为{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语言实现
package main
import "fmt"
type Node struct {
Server string
Weight int
Current int
}
//平滑轮询调度实现
func SelectNext(nodes []*Node) (best *Node) {
if len(nodes) == 0 {
return nil
}
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 nil
}
//选中节点的当前值减去所有节点的权重总和
best.Current -= total
return best
}
func main() {
nodes := []*Node{
{Server: "127.0.0.1", Weight: 5},
{Server: "127.0.0.2", Weight: 1},
{Server: "127.0.0.3", Weight: 1},
}
for i := 0; i < 10; i++ {
if best := SelectNext(nodes); best != nil {
fmt.Println(i, "=>", best.Server, best.Weight)
}
}
}
运行输出:
0 => 127.0.0.1 5
1 => 127.0.0.1 5
2 => 127.0.0.2 1
3 => 127.0.0.1 5
4 => 127.0.0.3 1
5 => 127.0.0.1 5
6 => 127.0.0.1 5
7 => 127.0.0.1 5
8 => 127.0.0.1 5
9 => 127.0.0.2 1
小结
这里简单做了总结笔记,很多开源项目的基于权重的轮询算法也是参考了此算法,比如著名的Go语言RPC框架https://github.com/smallnest/rpcx。
关于如何证明算法的平滑性,感兴趣的可以阅读下面的参考链接: