Fellow Travellers

负载均衡 常用算法

万世威
字数统计: 1.2k阅读时长: 6 min
2019/02/22 Share

负载均衡

数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ServerIps {
public static List<String> ipList = new ArrayList<>();
static {
ipList.add("192.168.0.1");
ipList.add("192.168.0.2");
ipList.add("192.168.0.3");
ipList.add("192.168.0.4");
ipList.add("192.168.0.5");
ipList.add("192.168.0.6");
ipList.add("192.168.0.7");
ipList.add("192.168.0.8");
ipList.add("192.168.0.9");
}
// 权重数据集
public static Map<String, Integer> weightList = new HashMap<>();
static {
weightList.put("192.168.0.1", 2);
weightList.put("192.168.0.2", 8);
weightList.put("192.168.0.3", 2);
weightList.put("192.168.0.4", 3);
weightList.put("192.168.0.5", 4);
weightList.put("192.168.0.6", 6);
weightList.put("192.168.0.7", 3);
weightList.put("192.168.0.8", 5);
weightList.put("192.168.0.9", 7);
}
}
  • 随机
1
2
3
4
5
// 机器性能不一样时,资源浪费,给每台机器设置权重
private static String getIp() {
int pos = random.nextInt(ServerIps.ipList.size());
return ServerIps.ipList.get(pos);
}
  • 加权随机
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* 方式一:
* 根据权重,添加多个记录到 ips 中 再采用 随机的方法
* 缺点:权重过大时, ips过大
*/
private static List<String> ips = new ArrayList<>();
static {
for (String ip : ServerIps.weightList.keySet()) {
Integer weightNum = ServerIps.weightList.get(ip);
for (Integer i = 0; i < weightNum; i++) {
ips.add(ip);
}
}
}

/**
* 方式二:
* A:5
* B:3
* C:2
* weightCount = 10
* r = random [0,10) ; 8
* r = 8 - 5 = 3; 3 < 0; false
* r = 3 - 3 = 0; 0 < 0; false
* r = 0 - 2 = -2; -2 < 0; true; return ip
*/
private static Integer weightCount = 0;
static {
for (Integer integer : ServerIps.weightList.values()) {
weightCount += integer;
}
System.out.println("weightCount = " + weightCount);
}
private static String getIps() {
int r = random.nextInt(weightCount);
System.out.print("r = " + r);
for (String ip : ServerIps.weightList.keySet()) {
Integer weight = ServerIps.weightList.get(ip);
r = r - weight;
if (r < 0) {
return ip;
}
}
return null;
}
  • 轮询
1
2
3
4
5
6
private static Integer pos = 0;
private static String getIp() {
String ip = ServerIps.ipList.get(pos);
pos = (pos + 1) % ServerIps.ipList.size();
return ip;
}
  • 加权轮询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 方式一:加权轮询
* 和随机一样,根据权重,复制到 List 中
*/
private static List<String> ipList = new ArrayList<>();
static {
for (String ip : ServerIps.weightList.keySet()) {
Integer weight = ServerIps.weightList.get(ip);
for (Integer num = 0; num < weight; num++) {
ipList.add(ip);
}
}
}
private static Integer pos = 0;
private static String getIp() {
String ip = ipList.get(pos);
pos = (pos + 1) % ipList.size();
return ip;
}

/**
* 方式二:加权轮询
* 和随机算法中方式二类似,随机数采用 递增方式,达到轮询的方式
*/
private static Integer weightCount = 0;
static {
for (Integer integer : ServerIps.weightList.values()) {
weightCount += integer;
}
System.out.println("weightCount = " + weightCount);
}
private static String getIps() {
int r = pos;
for (String ip : ServerIps.weightList.keySet()) {
Integer weight = ServerIps.weightList.get(ip);
r = r - weight;
if (r < 0) {
pos = (pos + 1) % weightCount;
return ip;
}
}
return null;
}
  • 平滑加权轮询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 方式三:平滑加权轮询
* A:5
* B:1
* C:1
* AAAAABC : 权重过大时,会一直连续请求 A
* AABACAA
* weight 5,1,1 静态
* sum(weight) 7 静态
* currentweight(cw) 0,0,0 动态
* cw += weight max(cw) ip max(cw) -= sum(weight)
* 5,1,1 5 A -2,1,1
* 3,2,2 3 A -4,2,2
* 1,3,3 3 B 1,-4,3
* 6,-3,4 6 A -1,-3,4
* 4,-2,5 5 C 4,-2,-2
* 9,-1,-1 9 A 2,-1,-1
* 7,0,0 7 A 0,0,0
* 开始新一轮
*/
private static Map<String, Weight> weightMap = new HashMap<>();

private static Integer totalWeight;

static {
weightMap.put("A", new Weight(5, "A", 5));
weightMap.put("B", new Weight(1, "B", 1));
weightMap.put("C", new Weight(1, "C", 1));
totalWeight = weightMap.values().stream().map(Weight::getW).reduce(0, (w1, w2) -> w1 + w2);
}

private static String getIp() {
Weight maxWeight = null;
// 获取到最大的权重Weight对象
for (Weight weight : weightMap.values()) {
if (maxWeight == null || weight.getCw() > maxWeight.getCw()) {
maxWeight = weight;
}
}
// 最大的权重的Weight对象,修改CurrentWeight值
maxWeight.setCw(maxWeight.getCw() - totalWeight);
// cw+weight
for (Weight weight : weightMap.values()) {
weight.setCw(weight.getCw() + weight.getW());
}
return maxWeight.getIp();
}
  • 一致性Hash

​ 构造一个长度为232的整数环(一致性Hash环),然后根据节点名称的Hash值(分布在0 - 232-1)将服务器节点放置在这个Hash环上。最后,根据数据的Key值计算得到其Hash值,在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。

​ 在服务器较少的情况下,会造成分布不均匀,引入 “虚拟节点” 用于解决数据倾斜问题。

img

每个Invoker会共创建160个虚拟节点,Hash环总长度为160*节点数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static TreeMap<Integer, String> virtualInvokers = new TreeMap<>();
// 虚拟节点数量
private static int replicaNumber = 160;
static {
for (String ip : ServerIps.ipList) {
for (int i = 0; i < replicaNumber; i++) {
int hash = getHash(ip + "VN" + i);
virtualInvokers.put(hash, ip);
}
}
}
// 选取节点
private String selectForKey(int hash) {
Map.Entry<Integer, String> entry = virtualInvokers.tailMap(hash, true).firstEntry();
if (entry == null) {
entry = virtualInvokers.firstEntry();
}
return entry.getValue();
}
  • 最小活跃数

​ 活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求,此时应优先将请求分配给该服务提供者。

​ Dubbo会为每个服务提供者Invoker分配一个active,代表活跃数大小。调用之前做自增操作,调用完成后做自减操作。这样有的服务处理的快,有的处理的慢。越快的,active数量越小,就优先分配。

CATALOG
  1. 1. 负载均衡