我在REST API应用网关负载均衡中加权轮循方法的实现

最近项目需要一个REST API应用网关,因此用GO写了一个,功能是在不断完善的,感觉最终功能跟nginx反向代理差不多的,除了业务处理。现在已经实现通过配置来控制转发行为。请求前端定义的地址A,通过认证后,转到后端指定地址B,,并把结果返回。

现在增加支持多backend,因此要实现负载均衡。

大概网上看了下nginx源代码的算法,及其它网友的,基本上都是实时计算,最终落到backend的顺序,都是顺序的。

例如网友的例子:

{ a, b, c }三个服务器,weight值是{ 5, 1, 2 }

加权轮询的选择的服务器顺序是:a,c,a,a,b,a,c,a

当轮询一遍后,又重新一遍,不断重复。

这样看来,每次计算权重获取backend是没有必要了,不如把获取backend的顺序计算好了,放到队列里,按顺序取就好了。

对上面这个例子,我的实现基本思路是:

1.在启动时读取配置文件并初始化,把所有的backend放到队列MasterQ里,

例如三个backend:MasterQ=[0:backend1,1:backend2,2:backend3]

如果是使用加权轮循算法,初始化加权轮循算法对象

2.初始化加权轮循算法对象。

把权重值累加,weight_sum=5+1+2=8(可除最大公约后再累加)

初始队列RoundRobinQ[weight_sum]

填充RoundRobinQ=[0,0,0,0,0,1,2,2],值是MasterQ的索引

打乱RoundRobinQ顺序,算法自定:RoundRobinQ=[1 0 1 0 2 0 1 0 2 0]

3.在每次请求到来,要获取一个backend时,执行next方法:

直接从根据当前RoundRobinQ的索引值,获取MasterQ中可用的backend:

backend =MasterQ[RoundRobinQ[index]]

获取后index++

以上。这样的话,每次请求获取一个backend的计算量就比较少。

当然,我是想着换一个思路并实现,而且貌似也比较简单。

下面是第一版代码片断,可以参考一下,已经支持把不可用的backend剔除掉:

//负载均衡的配置信息type LBConf struct {BaseUrlstringWeightintCookiestringMaxFails int //连接几次后标记为该连接不可用FailTimeout int //连接超过多少秒时间后标记该连接不可用Backupint //是否备份服务器 0:no, 1:yes}//负载均衡的backend信息type LBNode struct {ConfLBConfDownboolFailsint //连续失败的次数,成功后置为0LastDownTime int64}func (this *LBNode) SetFail() {this.Fails++if this.Fails > this.Conf.MaxFails {this.Down = truethis.LastDownTime = time.Now().Unix()}}//负载均衡算法接口,不同的算法实现这个接口即可type LBInterface interface {Init()Next(req *http.Request) *LBNode}//负载均衡运行时//保存公共的配置信息type LBRuntime struct {LBint//load balance 类型MasterQ []*LBNode //可用的节点BackupQ []*LBNode}func (this *LBRuntime) Next(Req *http.Request) *LBNode {return nil}func (this *LBRuntime) Init() {}//负载均衡实现方式:加权轮循//具体可以参考ngnix的实现。//这里实现方式有点区别,没那么复杂//实现LBInterface接口,继承LBRuntimetype LBRountRobin struct {LBRuntimeRoundRobinQ []int //轮循队列RBIndexint //当前RoundRobinQ的下标MQLenint //MasterQ队列长度RQLenint //RoundRobinQ队列长度}//获取下一个节点//根据初始化的加权轮循队列,按顺序循环获取后端节点func (this *LBRountRobin) Next(req *http.Request) *LBNode {if this.RQLen == 1 {c := this.MasterQ[this.RoundRobinQ[0]]if c.Down {this.RQLen = 0return nil}return c} else if this.RQLen > 1 {//多线程这里会不安全(当其它线程调用init时),不过接受//通过复制来防止并发修改,防止下标超标index := this.RBIndexthis.RBIndex++if this.RBIndex >= this.RQLen {this.RBIndex = 0}if index >= this.RQLen {index = 0}c := this.MasterQ[this.RoundRobinQ[index]]if c.Down {//如果down了,重新生成可用的队列,取下一个节点this.Init()return this.Next(req)}return c}//TODO 没有主节点,选择备用节点return nil}//初始化Round-Robin负载均衡//根据加权轮循得出的结果是周期循环的,这里初始化时简单地实现一个周期循环队列//而不像NGINX在每个请求到来时再计算func (this *LBRountRobin) Init() {weight_sum := 0numQ := 0if len(this.MasterQ) < 1 {//如果没有backend节点,直接返回return}for _, node := range this.MasterQ {if node.Down {//过滤掉挂掉的节点continue}numQ++weight_sum += node.Conf.Weight}if numQ == 0 {this.RBIndex = 0this.RQLen = 0this.MQLen = len(this.MasterQ)this.RoundRobinQ = nillog.Info("Round Robin Q: [ ]")return}//把每次执行的下标预先放到队列里,执行的时候按顺序就行了if numQ == 1 {this.RoundRobinQ = make([]int, 1)} else {this.RoundRobinQ = make([]int, weight_sum)}w := 0for key, node := range this.MasterQ {if node.Down {//过滤掉挂掉的节点,只选择没挂的节点continue}if numQ == 1 {this.RoundRobinQ[0] = keybreak} else {for i := 0; i < node.Conf.Weight; i++ {this.RoundRobinQ[w+i] = key}}w = w + node.Conf.Weight}if numQ > 1 {//打乱RoundRobinQ的顺序 TODO 改进r := rand.New(rand.NewSource(time.Now().UnixNano()))for i := 0; i < weight_sum; i++ {x := r.Intn(weight_sum)temp := this.RoundRobinQ[x]other := weight_sum % (x + 1)this.RoundRobinQ[x] = this.RoundRobinQ[other]this.RoundRobinQ[other] = temp}}this.RBIndex = 0this.RQLen = len(this.RoundRobinQ)this.MQLen = len(this.MasterQ)log.Info("Round Robin Q: ", this.RoundRobinQ)}

可见内心底对旅行是多么的淡漠。

我在REST API应用网关负载均衡中加权轮循方法的实现

相关文章:

你感兴趣的文章:

标签云: