码迷,mamicode.com
首页 > Windows程序 > 详细

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

时间:2015-03-21 20:00:56      阅读:217      评论:0      收藏:0      [点我收藏+]

标签:golang   负载均衡   算法   加权轮循   


       最近项目需要一个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)
}


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

标签:golang   负载均衡   算法   加权轮循   

原文地址:http://blog.csdn.net/rariki/article/details/44518119

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!