负载均衡算法
随机分配
缺点:
随机取适用的场景是:各服务器处理能力都差不多(实际上是不太可能的)
不能满足我们 "能者多劳" 的诉求
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
:根据客户端携带的参数进行映射
最小活跃数