负载均衡笔记

本文介绍了负载均衡的各种算法,包括随机分配、加权随机分配、加权轮询、平衡加权轮询、一致性hash算法和最小活跃数。重点讨论了加权轮询的改进方法,以实现更均衡的分配,并解释了一致性hash算法如何处理服务器增减带来的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

负载均衡算法

随机分配

缺点:

随机取适用的场景是:各服务器处理能力都差不多(实际上是不太可能的)
不能满足我们 "能者多劳" 的诉求

func main() {
	servers := []string{
		"127.0.0.1:8080",
		"127.0.0.1:8081",
		"127.0.0.1:8082",
		"127.0.0.1:8083",
		"127.0.0.1:8084",
		"127.0.0.1:8085",}
	for i := 0; i < 10; i++ {
		fmt.Println(getServer1(servers))
	}
}


func getServer1(servers []string) string {
	return servers[rand.Intn(len(servers))]

加权随机分配(能者多劳)

func main() {
	servers_m := map[string]int{
		"127.0.0.1:8080": 2,
		"127.0.0.1:8081": 8,
		"127.0.0.1:8082": 1,
		"127.0.0.1:8083": 9,
		"127.0.0.1:8084": 4,
		"127.0.0.1:8085": 6,
	}
	m := make(map[string]int)
	for i := 0; i < 10000; i++ {
		ip := getServer2(servers_m)
		v, ok := m[ip]
		if ok {
			m[ip] = v + 1
		} else {
			m[ip] = 1
		}
	}
	fmt.Printf("%+v", m)
}

func getServer2(servers map[string]int) string {
	// 所有权重总和,作为坐标轴
	var totalWeight int
	for _, v := range servers {
		totalWeight += v
	}

	// 随机生成坐标点
	offset := rand.Intn(totalWeight)
	// 找寻坐标点在坐标轴的位置 以及对应的IP
	for ip, weight := range servers {
		if offset < weight {
			return ip
		}
		offset -= weight
	}
	return ""
}

// 控制台打印结果
map[
127.0.0.1:8080:643 
127.0.0.1:8081:2723 
127.0.0.1:8082:346 
127.0.0.1:8083:2970 
127.0.0.1:8084:1337 
127.0.0.1:8085:1981
]

加权轮询

代码是有问题的,golang没有LinkedHashMap

而这就是问题所在,虽然进行了轮询还是不够“平均”。

A:5 、B:1、C:1

请求进来,负责处理的机器分别是 AAAAABC,“旱的旱死涝的涝死”

我们期望理想中的是 AABACAA,在权重不受影响的情况下,能够相对均衡一些。

func main() {
	servers_m := map[string]int{
		"127.0.0.1:8080": 2,
		"127.0.0.1:8081": 8,
		"127.0.0.1:8082": 1,
		"127.0.0.1:8083": 9,
		"127.0.0.1:8084": 4,
		"127.0.0.1:8085": 6,
	}
	for i := 0; i < 2; i++ {
		fmt.Println(getServer3(servers_m))
	}
}

var num int
func getServer3(servers map[string]int) string {
	// 所有权重总和,作为坐标轴
	var totalWeight int
	for _, v := range servers {
		totalWeight += v
	}

	// 轮询生成坐标点(可以把num换成requestID)
	offset := num % totalWeight
	num += 1

	// 找寻坐标点在坐标轴的位置 以及对应的IP
	for ip, weight := range servers {
		if offset < weight {
			return ip
		}
		offset -= weight
	}
	return ""
}

平衡加权轮询

对加权轮询的弊端进行均衡。具体过程如下:

除了权重外,引入新的概念 currentWeight,可以理解为一个动态变化的权重,每个IP对应一个,初始值为0

于是,我们在这个算法里需要用的几项分别为:IP、weight、currentWeight、totalWeight

第一步:所有的 currentWeight += weight(所有的 currentWeight 变化了一次)

第二步:找出此时最大的 currentWeight

第三步:这个最大的 currentWeight 所对应的IP就是这次分配的结果

第四部:把这个 最大的 currentWeight -= totalWeight(最大的 currentWeight 变化了一次)

以此类推,重复上述4个步骤,可知,每一轮最大的 currentWeight 变化了两次。

以3个IP:A、B、C,权重分别为5、1、1为例,详细这个精巧的计算过程

1:all currentWeight += weight     2:max(all currentWeight)      3:得到result      4:max_currentWeight -= totalWeight

       0,0,0   -> 5,1,1                             max_currentWeight = 5           result = A             5 - 7 = -2

       -2,1,1  -> 3,2,2                             max_currentWeight = 3           result = A             3 - 7 = -4

       -4,2,2  -> 1,3,3                             max_currentWeight = 3           result = B             3 - 7 = -4

       1,-4,3  ->  6,-3,4                           max_currentWeight = 6           result = A              6 - 7 = -1

      -1,-3,4  -> 4,-2,5                            max_currentWeight = 5           result = C             5 - 7 = -2

       4,-2,-2  -> 9,-1,-1                          max_currentWeight = 9           result = A              9 - 7 = 2

       2,-1,-1  -> 7,0,0                             max_currentWeight = 7          result = A               7 - 7 = 0

       0,0,0 ->    5,1,1  (又回到最初的起点......)     

       这样一轮的IP分配为:AABACAA,而不是原来的 AAAAABC

func main() {
	servers_m := map[string]int{
		"A": 5,
		"B": 1,
		"C": 1,
	}
	// 初始化
	m := make(map[string]*IPWeight)
	for k, v := range servers_m {
		m[k] = &IPWeight{
			IP:            k,
			Weight:        v,
			CurrentWeight: 0,
		}
	}
	for i := 0; i < 15; i++ {
		fmt.Println(getServer4(m))
	}
}

func getServer4(m map[string]*IPWeight) (result string) {
	//fmt.Printf("方法入参 %+v \n", m)
	// 所有权重总和
	var totalWeight int
	for _, v := range m {
		totalWeight += v.Weight
	}

	// 1:currentWeight += weight
	for _, v := range m {
		v.CurrentWeight = v.CurrentWeight + v.Weight
	}
	//fmt.Printf("执行 currentWeight += weight %+v \n", m)
	// 2:max(currentWeight)
	maxCurrentWeight := &IPWeight{}
	for _, v := range m {
		if v.CurrentWeight > maxCurrentWeight.CurrentWeight {
			maxCurrentWeight = v
		}
	}
	result = maxCurrentWeight.IP
	// 3、max(currentWeight) -= totalWeight
	m[maxCurrentWeight.IP].CurrentWeight -= totalWeight
	//fmt.Printf("执行 max(currentWeight) -= totalWeight %+v \n", m)
	return
}

type IPWeight struct {
	IP            string
	Weight        int
	CurrentWeight int
}

func (i IPWeight) String() string {
	out, err := json.Marshal(i)
	if err != nil {
		return err.Error()
	}
	return string(out)
}

// 打印结果 有2种情况, AABACAA 或 AACABAA,原因同上

一致性hash算法

和上面介绍的坐标轴一个道理,hash环上的hashcode是有序的。request计算出的hash值,落在IP1~IP2之间,走IP2这台服务器。

但是IP4下线后,会造成分配不均匀

为了解决这个问题,需要使用 虚拟节点

看一下Nginx官方文档中对于这块的描述(以下为中文翻译文档)

  • 这个模块提供一致性hash作为负载均衡算法。
  • 该模块通过使用客户端信息(如:$ip, $uri, $args等变量)作为参数,使用一致性hash算法将客户端映射到后端机器
  • 如果后端机器宕机,这请求会被迁移到其他机器
  • server id 字段,如果配置id字段,则使用id字段作为server标识,否则使用server ip和端口作为server标识,
    • 使用id字段可以手动设置server的标识,比如一台机器的ip或者端口变化,id仍然可以表示这台机器。使用id字段
    • 可以减低增减服务器时hash的波动。
  • server weight 字段,作为server权重,对应虚拟节点数目
  • 具体算法,将每个server虚拟成n个节点,均匀分布到hash环上,每次请求,
    根据配置的参数计算出一个hash值,在hash环上查找离这个hash最近的虚拟节点,对应的server作为该次请求的后端机器。
  • 该模块可以根据配置参数采取不同的方式将请求均匀映射到后端机器,比如:
    • consistent_hash $remote_addr:可以根据客户端ip映射
    • consistent_hash $request_uri: 根据客户端请求的uri映射
    • consistent_hash $args:根据客户端携带的参数进行映射

最小活跃数

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值