最近项目需要一个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 { BaseUrl string Weight int Cookie string MaxFails int //连接几次后标记为该连接不可用 FailTimeout int //连接超过多少秒时间后标记该连接不可用 Backup int //是否备份服务器 0:no, 1:yes } //负载均衡的backend信息 type LBNode struct { Conf LBConf Down bool Fails int //连续失败的次数,成功后置为0 LastDownTime int64 } func (this *LBNode) SetFail() { this.Fails++ if this.Fails > this.Conf.MaxFails { this.Down = true this.LastDownTime = time.Now().Unix() } } //负载均衡算法接口,不同的算法实现这个接口即可 type LBInterface interface { Init() Next(req *http.Request) *LBNode } //负载均衡运行时 //保存公共的配置信息 type LBRuntime struct { LB int //load balance 类型 MasterQ []*LBNode //可用的节点 BackupQ []*LBNode } func (this *LBRuntime) Next(Req *http.Request) *LBNode { return nil } func (this *LBRuntime) Init() { } //负载均衡实现方式:加权轮循 //具体可以参考ngnix的实现。 //这里实现方式有点区别,没那么复杂 //实现LBInterface接口,继承LBRuntime type LBRountRobin struct { LBRuntime RoundRobinQ []int //轮循队列 RBIndex int //当前RoundRobinQ的下标 MQLen int //MasterQ队列长度 RQLen int //RoundRobinQ队列长度 } //获取下一个节点 //根据初始化的加权轮循队列,按顺序循环获取后端节点 func (this *LBRountRobin) Next(req *http.Request) *LBNode { if this.RQLen == 1 { c := this.MasterQ[this.RoundRobinQ[0]] if c.Down { this.RQLen = 0 return nil } return c } else if this.RQLen > 1 { //多线程这里会不安全(当其它线程调用init时),不过接受 //通过复制来防止并发修改,防止下标超标 index := this.RBIndex this.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 := 0 numQ := 0 if 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 = 0 this.RQLen = 0 this.MQLen = len(this.MasterQ) this.RoundRobinQ = nil log.Info("Round Robin Q: [ ]") return } //把每次执行的下标预先放到队列里,执行的时候按顺序就行了 if numQ == 1 { this.RoundRobinQ = make([]int, 1) } else { this.RoundRobinQ = make([]int, weight_sum) } w := 0 for key, node := range this.MasterQ { if node.Down { //过滤掉挂掉的节点,只选择没挂的节点 continue } if numQ == 1 { this.RoundRobinQ[0] = key break } 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 = 0 this.RQLen = len(this.RoundRobinQ) this.MQLen = len(this.MasterQ) log.Info("Round Robin Q: ", this.RoundRobinQ) }
原文地址:http://blog.csdn.net/rariki/article/details/44518119