go简单实现几种常见的负载均衡

今天试试用go实现四种常见的负载均衡,分别是随机负载均衡,轮询负载均衡,加权负载均衡,一致性hash负载均衡。

随机负载均衡

功能和名字一样,随机从一堆服务器中选择一个服务器,那么实现也很简单,不做过多说明。使用了rand方法随机取一个服务器。

package main

import (
	"errors"
	"fmt"
	"math/rand"
)

type RandomBalance struct {
	curIndex 	int
	res		 	[]string
}

func (r *RandomBalance)Add(params ...string) error{
	if len(params) == 0{
		return errors.New("params need more than 0")
	}
	addr := params[0]
	r.res = append(r.res,addr)
	return nil
}
func (r *RandomBalance)Next() string{
	if len(r.res) == 0{
		return ""
	}
	r.curIndex = rand.Intn(len(r.res))
	return r.res[r.curIndex]
}

func (r *RandomBalance)Get() (string,error){
	return r.Next(),nil
}

func main() {
	r := new(RandomBalance)
	r.Add("localhost:8080")
	r.Add("localhost:8081")
	r.Add("localhost:8082")
	r.Add("localhost:8083")
	r.Add("localhost:8084")
	r.Add("localhost:8085")
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
}

轮询负载均衡

这个就要注意轮询到了结尾(0-1-2-3),如果后面没有服务器要重新轮询回到0。这个就直接用取模就可以完成了。

package main

import (
	"errors"
	"fmt"
)

type RoundRobinBalance struct {
	curIndex 	int
	res		 	[]string
}

func (r *RoundRobinBalance)Add(params ...string) error{
	if len(params) == 0{
		return errors.New("params need more than 0")
	}
	addr := params[0]
	r.res = append(r.res,addr)
	return nil
}
func (r *RoundRobinBalance)Next() string{
	if len(r.res) == 0{
		return ""
	}
	res := r.res[r.curIndex]
	r.curIndex = (r.curIndex + 1) % len(r.res)
	return res
}

func (r *RoundRobinBalance)Get() (string,error){
	return r.Next(),nil
}

func main() {
	r := new(RoundRobinBalance)
	r.Add("localhost:8080")
	r.Add("localhost:8081")
	r.Add("localhost:8082")
	r.Add("localhost:8083")
	r.Add("localhost:8084")
	r.Add("localhost:8085")
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
}

加权负载均衡

这个加权就很有意思,每一个服务器有它当前的权值,我们选择权值最高的服务器作为访问服务器。
在这里插入图片描述

当然权值是会变化的,如何变化?先来看一张图。
在这里插入图片描述
我们默认选择currentWeight作为选择的标准,每次都选择最大的currentWeight,每轮选择结束后被选择的currentWeight也会对应的改变,就这么一轮一轮的选择。

package main

import (
	"errors"
	"fmt"
	"strconv"
)

type WeightNode struct {
	Addr string
	Weight int64
	EffectiveWeight int64
	CurrentWeight int64
}

type WeightRoundLoadBalance struct {
	list []*WeightNode

}

func (r *WeightRoundLoadBalance)Add(params ...string) error{
	if len(params) != 2{
		return errors.New("params need more 2")
	}
	parseInt, err := strconv.ParseInt(params[1], 10, 64)
	if err != nil{
		return errors.New("convert to int error")
	}
	newNode := &WeightNode{
		Addr:            params[0],
		Weight:          parseInt,
		EffectiveWeight: parseInt,
		CurrentWeight: parseInt,
	}
	r.list = append(r.list,newNode)
	return nil
}
func (r *WeightRoundLoadBalance)Next() string{
	var total int64 = 0
	var best *WeightNode
	for index,v := range r.list{
		total += v.CurrentWeight
		r.list[index].CurrentWeight += r.list[index].EffectiveWeight
		if v.EffectiveWeight < v.CurrentWeight{
			v.EffectiveWeight++
		}
		if best == nil || best.CurrentWeight < v.CurrentWeight{
			best = v
		}
	}
	if best == nil{
		return ""
	}
	best.CurrentWeight -= total
	return best.Addr
}

func (r *WeightRoundLoadBalance)Get() (string,error){
	return r.Next(),nil
}

func main() {
	r := new(WeightRoundLoadBalance)
	r.Add("A","4")
	r.Add("B","3")
	r.Add("C","2")
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
	fmt.Println(r.Get())
}

一致性hash

在这里插入图片描述

先说说一致性hash相对于传统取模hash的优点吧。

对于传统的取模hash我们知道使用这样一个公式来计算从哪个服务器取出缓存的,即:hash(资源) % 服务器数量 .但是这样会有一个缺点,即增加了服务器后,取余数的数量会变化,会导致大量缓存失效。

所以这个时候一致性hash就出现了,hash是将服务器先hash到一个hash环,通过顺时针决定自己的缓存存在哪个服务器,那么看看增加了一个服务器的情况,此时假如增加一个E,可能会导致一部分选取C为服务器的缓存失效,但是其他缓存是可以继续使用的。这样就优于取模hash。

在这里插入图片描述

但是一致性hash会发生一个问题,hash偏斜。即hash缓存不均匀,即由于hash情况过于极端,所有的缓存选择了一个服务器放缓存。所以需要让服务器“数量尽可能多”——————创建虚拟节点

即我们一开始是A一个服务器,但是我们可以将一个A分解成多个A(虚拟节点),即下图:

在这里插入图片描述

那么我们读取缓存的时候,可以先找到虚拟节点,再找到缓存存储的真实节点,读出缓存。

了解了一些一致性hash的原理,我们可以开始编写一致性hash的代码。

package main

import (
	"errors"
	"fmt"
	"hash/crc32"
	"sort"
	"strconv"
	"sync"
)

type Hash func([]byte)uint32

type Uint32Slice []uint32

func (s Uint32Slice)Len() int{
	return len(s)
}
func (s Uint32Slice)Less(i,j int)bool{
	return s[i] < s[j]
}
func (s Uint32Slice)Swap(i,j int){
	s[i],s[j] = s[j],s[i]
}

type ConsistentHashBalance struct {
	mux sync.RWMutex
	hash Hash		//hash函数
	replicas int 	//复制因子
	keys Uint32Slice	//已排序的hash节点切片
	hashmap map[uint32]string	//key为hash值val为节点
}

func NewConsistenceHashBalance(replicas int,hash Hash)*ConsistentHashBalance{
	c := &ConsistentHashBalance{
		hash:     hash,
		replicas: replicas,
		hashmap:  make(map[uint32]string),
		keys: make([]uint32,0,100),
	}

	if c.hash == nil{
		//保证是一个2^32 - 1的一个环
		c.hash = crc32.ChecksumIEEE
	}
	return c
}

func (c *ConsistentHashBalance)Add(params ...string)error{
	if len(params) == 0{
		return errors.New("param need more than one")
	}
	addr := params[0]
	//计算虚拟节点hash值
	for i := 0;i < c.replicas;i++{
		hash := c.hash([]byte(strconv.Itoa(i) + addr))
		//实现了排序接口
		c.keys = append(c.keys,hash)
		c.hashmap[hash] = addr
	}
	sort.Sort(c.keys)
	return nil
}
//得到取缓存的服务器
func (c *ConsistentHashBalance)Next(key string)(string,error){
	if len(c.keys) == 0{
		return "",nil
	}
	hash := c.hash([]byte(key))
	//通过二分查询到最优节点(第一个hash大于资源hash的服务器)
	idx := sort.Search(len(c.keys), func(i int) bool {
		return c.keys[i] > hash
	})
	if idx == len(c.keys){
		//没有找到服务器,说明此时处于环的尾部,那么直接用第0台服务器
		idx = 0
	}
	c.mux.Lock()
	defer c.mux.Unlock()
	val := c.hashmap[c.keys[idx]]
	return val,nil
}
func main() {
	r := NewConsistenceHashBalance(3,nil)
	r.Add("localhost:8001")
	r.Add("localhost:8002")
	r.Add("localhost:8003")

	fmt.Println(r.Next("localhost:8000/hello"))
	fmt.Println(r.Next("localhost:8000/hello1"))
	fmt.Println(r.Next("localhost:8000/hello2"))

}

由于楼主水平有限,可能对于hash一致性的描述并不是很清楚,强烈推荐大家看B站的讲解,讲的特别好,时长不长,但是可以对一致性hash会有一个最基本的了解。
下面贴出地址:https://www.bilibili.com/video/BV1Hs411j73wfrom=search&seid=10071461099033302500&spm_id_from=333.337.0.0

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Golang 中,使用 `select` 语句可以轻松地实现简单负载均衡。假设有多个服务器地址,我们可以通过 `select` 语句来选择其中一个地址进行请求: ```go package main import ( "fmt" "net/http" ) func main() { // 定义多个服务器地址 servers := []string{ "http://localhost:8080", "http://localhost:8081", "http://localhost:8082", } // 构造 http.Client client := &http.Client{} // 循环请求服务器 for { // 使用 select 语句选择服务器 select { case server := <-getServerChan(servers): // 构造请求 req, err := http.NewRequest("GET", server, nil) if err != nil { fmt.Printf("Error creating request: %s\n", err) continue } // 发送请求 resp, err := client.Do(req) if err != nil { fmt.Printf("Error sending request to %s: %s\n", server, err) continue } // 处理响应 fmt.Printf("Response from %s: %s\n", server, resp.Status) resp.Body.Close() } } } // 返回可用的服务器地址 func getServerChan(servers []string) chan string { serverChan := make(chan string) go func() { for { for _, server := range servers { serverChan <- server } } }() return serverChan } ``` 在上面的例子中,我们定义了多个服务器地址,然后通过 `select` 语句选择其中一个地址进行请求。`getServerChan` 函数会返回一个可用的服务器地址,循环中不断从该通道中获取可用的地址,然后构造请求,发送请求,处理响应。如果发送请求或处理响应出现错误,我们会记录日志并继续循环。 需要注意的是,这只是一个简单负载均衡实现,它并没有考虑服务器的负载情况。如果需要更复杂的负载均衡策略,可以考虑使用第三方库或者自己实现算法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值