BFE负载均衡源码--平滑加权轮询算法及实现

343 阅读3分钟

前言

上一篇文章我们分析了BFE的负载均衡算法--简单负载均衡算法的设计与实现。从宏观角度看算法可以满足针对不同权重的实例调度, 从微观角度看, 在某些特殊的权重配置下, 可能存在瞬时的对于某个实例的集中调度, 这种不平滑的负载可能会使某些实例出现瞬时高负载的现象,导致系统存在宕机的风险。比如后端有11个实例, 记为b1...b11, 其中b1权重为10, b2...b11的权重都为1, 则在调度的后期流量全部集中于b1。所以引出平滑加权轮询算法的实现。

前期回顾: 简单加权轮询算法设计与实现

算法描述

场景设定

  1. 假设当前有N个服务实例, 记为: S1, S2...SN
  2. 每个实例都有指定的权重配置, 记为: W1, W2...WN, 则总权重记为TW, TW=W1+W2+...+WN
  3. 每个实例都有一个当前有效权重, 记为: CW1, CW2...CWN, 初始化CWi = Wi

执行描述

  1. 初始化: 初始化每个实例当前有效权重等于配置的权重, 即CWi = Wi, 并且计算所有实例权重和TW
  2. 选择实例: 当前有效权重最大的实例则为选中的实例。
  3. 重新计算当前每个实例的有效权重:
    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
}

执行结果如下, 和分析结果一致:

image.png

源码实现

加权轮询算法实现在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
}

思考及总结

平滑加权轮询算法可以更均衡的在我们配置的权重下执行调度, 在理想的情况下并没有太多问题, 但在实际的应用场景中后端服务在处理业务时不可避免的会有超时, 异常中断等场景, 这些情况下分配策略并不能很好的满足我们对于负载均衡的调度管理, 基于真实的业务调度场景, 引申出了最小连接数负载均衡算法, 我们下期讨论 :)