1. 概述
负载均衡(LoadBalance)是微服务架构的重要能力。负载均衡是按照一定的策略,将请求分配给服务器。 举个例子,一主三从的数据库架构,当客户端的查询请求选择请求从库时,可以选择直连一台从库工作,但从库集群部署的意义就失去了。此时就需要有负载均衡,根据策略将请求分配给从库阶段。 通过负载均衡分配流量,提高系统的可用性。
由谁来实现和负责负载均衡?可以是中心代理服务,可以是由sidecar程序调度,甚至可以是在客户端框架层实现,取决使用的基础架构能力。
2. 负载均衡策略
负载均衡有一些常用的策略。
完全随机 每一个请求到来,均随机分配给集群节点。
轮询 按顺序分配给集群节点。
权重轮询 每个集群节点都有一个权重值,按照权重值轮询,权重越高分配的请求越多。
哈希 基于特定key哈希(如IP、Cookie等),固定分配到某一个集群节点。比如http层保持用户Session;数据库主从分离查询,从库实例的选择上,可以基于用户ID做哈希负载均衡,进而实现「单调读」。
一致性哈希 将哈希结果映射到哈希环上,比起普通哈希策略,使用一致性哈希策略能更加友好的应对节点变更的场景。 普通哈希会使得增加或者删除节点时会使得原有的hash结果大规模失效,比如节点数从5 -> 7,原先hash值15 mod 5 = 0节点,变更后 15 mod 7 = 1节点。而使用hash环只会影响新节点临近的节点。
3. 权重轮询的实现
权重轮询一般特指平滑权重轮询,算法核心思想是,每一轮筛选,节点当前权重都增加该节点初始的权重值,选择权重最高的节点,被选中的节点在减去总权重值,每一轮筛选所有节点的权重总和都保持不变。算法核心逻辑用Golang表达如下
var selected *Node
totalWeight := 0
for _, node := range w.Nodes {
node.CurrentWeight += node.Weight
totalWeight += node.Weight
if selected == nil || node.CurrentWeight > selected.CurrentWeight {
selected = node
}
}
selected.CurrentWeight -= totalWeight
return selected
Nginx实现的算法:github.com/nginx/nginx…
模拟代码实现
type WRRLB struct {
Nodes []*Node
}
type Node struct {
Name string
Weight int
CurrentWeight int
}
func NewWRRLB() *WRRLB {
return &WRRLB{}
}
func (w *WRRLB) AddNode(node *Node) {
w.Nodes = append(w.Nodes, node)
}
func (w *WRRLB) Pick() *Node {
var selected *Node
totalWeight := 0
for _, node := range w.Nodes {
node.CurrentWeight += node.Weight
totalWeight += node.Weight
if selected == nil || node.CurrentWeight > selected.CurrentWeight {
selected = node
}
}
selected.CurrentWeight -= totalWeight
return selected
}
测试用例:使用三个节点命名A、B、C,权重分别为5、3、1,调度18次 运行结果:A选择了10次,B选择6次,C选择2次,符合权重比例。
func main() {
lb := wrrlb.NewWRRLB()
nodeA := &wrrlb.Node{Name: "A", Weight: 5}
nodeB := &wrrlb.Node{Name: "B", Weight: 3}
nodeC := &wrrlb.Node{Name: "C", Weight: 1}
lb.AddNode(nodeA)
lb.AddNode(nodeB)
lb.AddNode(nodeC)
times := 18
count := make(map[string]int)
for i := 0; i < times; i++ {
node := lb.Pick()
count[node.Name]++
fmt.Println("Pick node:", node.Name)
}
fmt.Println("Count:", count)
}
4. 动态负载均衡
以权重轮询作为基础,负载均衡器可以增强节点权重的计算逻辑,实现动态负载均衡。 如平台人工调整权重,实时生效;灰度发布场景(如金丝雀发布);最常见的是根据节点的负载情况,,比如实例的错误率、延时、请求排队时间,动态计算一个权重(如故障实例自动剔除),滚动发布。
模拟节点下线 在上述模拟的代码中,增加一个goroutine,在500ms后,将A节点权重调整为0,模拟下线状态。查看运行效果,调整后A阶段不再被选择
go func() {
select {
case <-time.After(500 * time.Millisecond):
nodeA.Weight = 0
fmt.Println("Node A weight updated to 0")
}
}()
times := 18
count := make(map[string]int)
for i := 0; i < times; i++ {
node := lb.Pick()
count[node.Name]++
fmt.Println("Pick node:", node.Name)
time.Sleep(100 * time.Millisecond)
}
fmt.Println("Count:", count)
| 标题 | |
|---|---|