本章内容:
1.轮询
2.最少链接
3.随机算法
4.源地址哈希法
5.加权轮询、加权最少连接、加权随机
【准备:客户端请求IP】
1 public class IpMap
2 {
3 // 待路由的Ip列表,Key代表Ip,Value代表该Ip的权重
4 public static HashMap<String, Integer> serverWeightMap = new HashMap<String, Integer>();
5
6 static
7 {
8 serverWeightMap.put("192.168.1.100", 1);
9 serverWeightMap.put("192.168.1.101", 1);
10 // 权重为4
11 serverWeightMap.put("192.168.1.102", 4);
12 serverWeightMap.put("192.168.1.103", 1);
13 serverWeightMap.put("192.168.1.104", 1);
14 // 权重为3
15 serverWeightMap.put("192.168.1.105", 3);
16 serverWeightMap.put("192.168.1.106", 1);
17 // 权重为2
18 serverWeightMap.put("192.168.1.107", 2);
19 serverWeightMap.put("192.168.1.108", 1);
20 serverWeightMap.put("192.168.1.109", 1);
21 serverWeightMap.put("192.168.1.110", 1);
22 }
23 }
一、轮询(Round Robin)
轮询算法将每个请求轮流发送到每个服务器上。(一碗水端平)
(接收到六个请求,平均轮次分发给两个服务器进行处理。)
1 public class RoundRobin
2 {
3 private static Integer pos = 0;
4
5 public static String getServer()
6 {
7 // 重建一个Map,避免服务器的上下线导致的并发问题
8 Map<String, Integer> serverMap = new HashMap<String, Integer>();
9 serverMap.putAll(IpMap.serverWeightMap);
10
11 // 取得Ip地址List
12 Set<String> keySet = serverMap.keySet();
13 ArrayList<String> keyList = new ArrayList<String>();
14 keyList.addAll(keySet);
15
16 String server = null;
17 synchronized (pos)
18 {
19 if (pos > keySet.size())
20 pos = 0;
21 server = keyList.get(pos);
22 pos ++;
23 }
24
25 return server;
26 }
27 }
由于serverWeightMap中的地址列表是动态的,随时可能有机器上线、下线或者宕机,因此为了避免可能出现的并发问题,方法内部要新建局部变量serverMap,现将serverMap中的内容复制到线程本地,以避免被多个线程修改。这样可能会引入新的问题,复制以后serverWeightMap的修改无法反映给serverMap,也就是说这一轮选择服务器的过程中,新增服务器或者下线服务器,负载均衡算法将无法获知。新增无所谓,如果有服务器下线或者宕机,那么可能会访问到不存在的地址。因此,服务调用端需要有相应的容错处理,比如重新发起一次server选择并调用。
对于当前轮询的位置变量pos,为了保证服务器选择的顺序性,需要在操作时对其加锁,使得同一时刻只能有一个线程可以修改pos的值,否则当pos变量被并发修改,则无法保证服务器选择的顺序性,甚至有可能导致keyList数组越界。
优点:试图做到请求转移的绝对平衡。
缺点:为了保证请求转移的公平性,必须保证pos互斥性,需要引入重量级悲观锁synchronized,这会导致该段代码的并发量产生明显下降。
应用场景:适合每个服务器的性能都差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器无法承担过大的负载。(不能因材施教)如下图,server2和server1相比太菜了,应该少分点。
二、最少链接(Least Connections)
由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数过大,而另一台服务器的连接过小,造成负载不均衡。最少连接算法就是将请求发送给当前最少连接数的服务器上。(哇!有的对手太能打了,持久战啊,再来新的对手可不能让我来对付了,得让现在对手少的人来处理,我不行了不行了)
例如下图中,(1, 3, 5) 请求会被发送到服务器 1,但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1;(2, 4, 6) 请求被发送到服务器 2,只有 (2) 的连接断开,此时 (6, 4) 请求连接服务器 2。该系统继续运行时,服务器 2 会承担过大的负载。(请求456都太能打了,Server2要应对两个难处理的,如果再来新的,得让Server1来处理)
三、随机算法
通过系统随机函数,根据后端服务器列表的大小值来随机选择其中一台进行访问。由概率统计理论可以得知,随着调用量的增大,其实际效果越来越接近于平均分配流量到每一台后端服务器,也就是轮询的效果。适用于每台服务器性能相近的情况。
1 public class Random
2 {
3 public static String getServer()
4 {
5 // 重建一个Map,避免服务器的上下线导致的并发问题
6 Map<String, Integer> serverMap = new HashMap<String, Integer>();
7 serverMap.putAll(IpMap.serverWeightMap);
8
9 // 取得Ip地址List
10 Set<String> keySet = serverMap.keySet();
11 ArrayList<String> keyList = new ArrayList<String>();
12 keyList.addAll(keySet);
13
14 java.util.Random random = new java.util.Random();
15 int randomPos = random.nextInt(keyList.size());
16
17 return keyList.get(randomPos);
18 }
19 }
整体代码思路和轮询法一致,先重建serverMap,再获取到server列表。在选取server的时候,通过Random的nextInt方法取0~keyList.size()区间的一个随机值,从而从服务器列表中随机获取到一台服务器地址进行返回。基于概率统计的理论,吞吐量越大,随机算法的效果越接近于轮询算法的效果。
四、源地址哈希法
源地址哈希通过对客户端 IP 计算哈希值之后,再对服务器数量取模得到目标服务器的序号。可以保证同一 IP 的客户端的请求会转发到同一台服务器上,用来实现会话粘滞(Sticky Session)。(我就是来报仇的,不着别人就找你)
1 public class Hash
2 {
3 public static String getServer()
4 {
5 // 重建一个Map,避免服务器的上下线导致的并发问题
6 Map<String, Integer> serverMap = new HashMap<String, Integer>();
7 serverMap.putAll(IpMap.serverWeightMap);
8
9 // 取得Ip地址List
10 Set<String> keySet = serverMap.keySet();
11 ArrayList<String> keyList = new ArrayList<String>();
12 keyList.addAll(keySet);
13
14 // 在Web应用中可通过HttpServlet的getRemoteIp方法获取
15 String remoteIp = "127.0.0.1";
16 int hashCode = remoteIp.hashCode();
17 int serverListSize = keyList.size();
18 int serverPos = hashCode % serverListSize;
19
20 return keyList.get(serverPos);
21 }
22 }
前两部分和轮询法、随机法一样就不说了,差别在于路由选择部分。通过客户端的ip也就是remoteIp,取得它的Hash值,对服务器列表的大小取模,结果便是选用的服务器在服务器列表中的索引值。
源地址哈希法的优点在于:保证了相同客户端IP地址将会被哈希到同一台后端服务器,直到后端服务器列表变更。根据此特性可以在服务消费者与服务提供者之间建立有状态的session会话。
源地址哈希算法的缺点在于:除非集群中服务器的非常稳定,基本不会上下线,否则一旦有服务器上线、下线,那么通过源地址哈希算法路由到的服务器是服务器上线、下线前路由到的服务器的概率非常低,如果是session则取不到session,如果是缓存则可能引发"雪崩"。
五、加权轮询、加权最少连接、加权随机算法(Weighted ~)
加权是针对每个服务器性能不一样而区别对待,为服务器赋予一定的权值,性能高的服务器分配更高的权值。能力越大责任越大。下面举例加权轮询,加权最少连接和加权随机实现原理、方法类似。
加权轮询是在轮询的基础上,根据服务器的性能差异,
(服务器Server1能一打五,所以来了6个请求时,照顾一下Server2)
1 public class WeightRoundRobin
2 {
3 private static Integer pos;
4
5 public static String getServer()
6 {
7 // 重建一个Map,避免服务器的上下线导致的并发问题
8 Map<String, Integer> serverMap = new HashMap<String, Integer>();
9 serverMap.putAll(IpMap.serverWeightMap);
10
11 // 取得Ip地址List
12 Set<String> keySet = serverMap.keySet();
13 Iterator<String> iterator = keySet.iterator();
14 //与轮询法类似,只是在获取服务器地址之前增加了一段权重计算的代码,根据权重的大小,
//将地址重复地增加到服务器地址列表中,权重越大,该服务器每轮所获得的请求数量越多。
15 List<String> serverList = new ArrayList<String>();
16 while (iterator.hasNext())
17 {
18 String server = iterator.next();
19 int weight = serverMap.get(server);
20 for (int i = 0; i < weight; i++)
21 serverList.add(server);
22 }
23
24 String server = null;
25 synchronized (pos)
26 {
27 if (pos > keySet.size())
28 pos = 0;
29 server = serverList.get(pos);
30 pos ++;
31 }
32
33 return server;
34 }
35 }