前言
上一篇文章我们分析了BFE的负载均衡算法--简单负载均衡算法的设计与实现。从宏观角度看算法可以满足针对不同权重的实例调度, 从微观角度看, 在某些特殊的权重配置下, 可能存在瞬时的对于某个实例的集中调度, 这种不平滑的负载可能会使某些实例出现瞬时高负载的现象,导致系统存在宕机的风险。比如后端有11个实例, 记为b1...b11
, 其中b1
权重为10, b2...b11
的权重都为1, 则在调度的后期流量全部集中于b1
。所以引出平滑加权轮询算法的实现。
前期回顾: 简单加权轮询算法设计与实现
算法描述
场景设定
- 假设当前有N个服务实例, 记为:
S1, S2...SN
。 - 每个实例都有指定的权重配置, 记为:
W1, W2...WN
, 则总权重记为TW
,TW=W1+W2+...+WN
。 - 每个实例都有一个当前有效权重, 记为:
CW1, CW2...CWN
, 初始化CWi = Wi
执行描述
- 初始化: 初始化每个实例当前有效权重等于配置的权重, 即
CWi = Wi
, 并且计算所有实例权重和TW
。 - 选择实例: 当前有效权重最大的实例则为选中的实例。
- 重新计算当前每个实例的有效权重:
a. 被选中的实例i执行:CWi = CWi(当前有效权重) - TW(总权重值) + Wi(配置权重)
b. 所有实例执行:CWi = CWi + Wi(配置权重)
执行图解
假设我们存在s1, s2, s3
, 三个实例, 每个实例对应的权重为: s1:6, s2:2, s3:1
, 则总权重TW
为9, 计算过程如下表:
请求 | 选中前的当前权重 | 选中的实例 |
---|---|---|
1 | {6, 2, 1} | s1 |
2 | {3, 4, 2} | s2 |
3 | {9, -3, 3} | s1 |
4 | {6, -1, 4} | s1 |
5 | {3, 1, 5} | s3 |
6 | {9, 3, -3} | s1 |
7 | {3, 7, -1} | s2 |
8 | {9, 0, 0} | s1 |
9 | {6, 2, 1} | s1 |
执行到第9次开始轮回, 可见平滑加权算法可以避免简单加权的集中调度风险。 |
代码实现
其中结构定义见我们之前文章: 简单加权轮询算法设计与实现 代码实现中的定义。
func (p *Wrr) SimthBalance() Backend {
var (
ret Backend
pos, total, max int
)
for idx, curBackend := range p.BackendList {
// 选择当前有效权重最大的实例, 并更新pos游标和最大有效权重
if curBackend.Current > max {
ret = curBackend
max = curBackend.Current
pos = idx
}
total += curBackend.Weight // 计算总权重
p.BackendList[idx].Current += p.BackendList[idx].Weight // 每个实例的当前有效权重 + 配置权重
}
p.BackendList[pos].Current -= total // 被选择的实例有效权重-总权重
return ret
}
执行结果如下, 和分析结果一致:
源码实现
加权轮询算法实现在bfe_balance/bal_slb/bal_rr.go:smoothBalance()
方法。
func smoothBalance(backs BackendList) (*backend.BfeBackend, error) {
var best *BackendRR
total, max := 0, 0
for _, backendRR := range backs {
backend := backendRR.backend
// skip ineligible backend
if !backend.Avail() || backendRR.weight <= 0 {
continue
}
// select backend with greatest current weight
if best == nil || backendRR.current > max {
best = backendRR
max = backendRR.current
}
total += backendRR.current
// update current weight
backendRR.current += backendRR.weight
}
if best == nil {
if bfe_debug.DebugBal {
log.Logger.Debug("rr_bal:reset backend weight")
}
return nil, fmt.Errorf("rr_bal:all backend is down")
}
// update current weight for chosen backend
best.current -= total
return best.backend, nil
}
思考及总结
平滑加权轮询算法可以更均衡的在我们配置的权重下执行调度, 在理想的情况下并没有太多问题, 但在实际的应用场景中后端服务在处理业务时不可避免的会有超时, 异常中断等场景, 这些情况下分配策略并不能很好的满足我们对于负载均衡
的调度管理, 基于真实的业务调度场景, 引申出了最小连接数负载均衡算法, 我们下期讨论 :)