在传统的系统中,应用程序、数据服务器、文件服务器等都在同一台机器上部署。随着互联网的发展,系统的访问量的增加,一台机器根本满足不了多用户的高并发访问,在
计算机领域,当单机性能达到瓶颈时,有两种方式可以解决性能问题,一是堆硬件,进一步提升配置,二是分布式,水平扩展。所以,假如一台机器不能
满足高并发的请求访问,那么就两台,两台还满足不了就增加到三台,这种方式我们称之为水平扩展,如何实现请求的平均分配便是负载均衡了。
负载均衡,英文名称(Load Balance),简称 LB。指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服
务器的辅助。通过某种负载分担技术,将外部发送来的请求均匀分配到对称结构中的某一台服务器上,而接收到请求的服务器独立地回应客户的请求。负载均衡能够
平均分配客户请求到服 务器阵列,借此提供快速获取重要数据,解决大量并发访问服务问题,这种集群技术可以用最少的投资获得接近于大型主机的性能。
那么如何来实现把请求均匀地分配到不同的分布式服务器上呢。这里介绍几种算法:
首先我们先定义一个map的IP地址,指每台服务器
public class IpMap
2 {
3 // 待路由的Ip列表,Key代表Ip,Value代表该Ip的权重
4 public static HashMap<String, Integer> serverWeightMap =
5 new HashMap<String, Integer>();
6
7 static
8 {
9 serverWeightMap.put("192.168.1.100", 1);
10 serverWeightMap.put("192.168.1.101", 1);
11 // 权重为4
12 serverWeightMap.put("192.168.1.102", 4);
13 serverWeightMap.put("192.168.1.103", 1);
14 serverWeightMap.put("192.168.1.104", 1);
15 // 权重为3
16 serverWeightMap.put("192.168.1.105", 3);
17 serverWeightMap.put("192.168.1.106", 1);
18 // 权重为2
19 serverWeightMap.put("192.168.1.107", 2);
20 serverWeightMap.put("192.168.1.108", 1);
21 serverWeightMap.put("192.168.1.109", 1);
22 serverWeightMap.put("192.168.1.110", 1);
23 }
24 }
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 =
9 new HashMap<String, Integer>();
10 serverMap.putAll(IpMap.serverWeightMap);
11
12 // 取得Ip地址List
13 Set<String> keySet = serverMap.keySet();
14 ArrayList<String> keyList = new ArrayList<String>();
15 keyList.addAll(keySet);
16
17 String server = null;
18 synchronized (pos)
19 {
20 if (pos > keySet.size())
21 pos = 0;
22 server = keyList.get(pos);
23 pos ++;
24 }
25
26 return server;
27 }
28 }
由于serverWeightMap中的服务器可能会出现新增、下线或者宕机,为了避免可能出现的并发问题(?),方法内部要新建局部变量serverMap,现将
serverMap中的内容复制到线程本地,以避免被多 个线程修改。这样可能会引入新的问题,复制以后serverWeightMap的修改无法反映给serverMap,也就是
说这一轮选择服务器的过程中, 新增服务器或者下线服务器,负载均衡算法将无法获知。新增无所谓,如果有服务器下线或者宕机,那么可能会访问到不存在的
地址。因此,服务调用端需要有相应的容错处理,比如重新发起一次server选择并调用。对于当前轮询的位置变量pos,为了保证服务器选择的顺序性,需要在操
作时对其加锁,使得同一时刻只能有一个线程可以修改pos的值。
2、随机法
public class Random
2 {
3 public static String getServer()
4 {
5 // 重建一个Map,避免服务器的上下线导致的并发问题
6 Map<String, Integer> serverMap =
7 new HashMap<String, Integer>();
8 serverMap.putAll(IpMap.serverWeightMap);
9
10 // 取得Ip地址List
11 Set<String> keySet = serverMap.keySet();
12 ArrayList<String> keyList = new ArrayList<String>();
13 keyList.addAll(keySet);
14
15 java.util.Random random = new java.util.Random();
16 int randomPos = random.nextInt(keyList.size());
17
18 return keyList.get(randomPos);
19 }
20 }
在选取server的时候,通过Random的nextInt方法取0~keyList.size()区间的一个随机值,然后获得服务器并返回。
3、哈希地址法
public class Hash
2 {
3 public static String getServer()
4 {
5 // 重建一个Map,避免服务器的上下线导致的并发问题
6 Map<String, Integer> serverMap =
7 new HashMap<String, Integer>();
8 serverMap.putAll(IpMap.serverWeightMap);
9
10 // 取得Ip地址List
11 Set<String> keySet = serverMap.keySet();
12 ArrayList<String> keyList = new ArrayList<String>();
13 keyList.addAll(keySet);
14
15 // 在Web应用中可通过HttpServlet的getRemoteIp方法获取
16 String remoteIp = "127.0.0.1";
17 int hashCode = remoteIp.hashCode();
18 int serverListSize = keyList.size();
19 int serverPos = hashCode % serverListSize;
20
21 return keyList.get(serverPos);
22 }
23 }
哈希算法就是通过客户端的IP地址,取得它的hash值,然后对服务器列表个数取模,来取得相应的服务器。
总结:
轮询算法:优点是基本能够实现请求的均衡分配;缺点是为了使得服务分配的顺序性,必须得引入悲观锁(synchronized),影响并发访问的效率。
随机算法:基于概率统计原理,吞吐量越大,随机发越能接近轮询法的平均效果。
hash算法:保证了相同客户端IP地址将会被哈希到同一台后端服务器,直到后端服务器列表变更。根据此特性可以在服务消费者与服务提供者之间建立有状态的
session会话。缺点是如果服务器发生上下线或者宕机的情况,会造成不能正确路由到以前所路由到的服务器上,如果是session则取不到session,如果是缓存则
可能引发"雪崩"。
4、加权轮询法或加权随机法
另外:由于不同服务器的配置不同,抗压能力也不尽相同,那么为了给那些配置好的多些负载,配置低的服务器少些负载,就引入了权重的概念。
public class WeightRoundRobin
2 {
3 private static Integer pos;
4
5 public static String getServer()
6 {
7 // 重建一个Map,避免服务器的上下线导致的并发问题
8 Map<String, Integer> serverMap =
9 new HashMap<String, Integer>();
10 serverMap.putAll(IpMap.serverWeightMap);
11
12 // 取得Ip地址List
13 Set<String> keySet = serverMap.keySet();
14 Iterator<String> iterator = keySet.iterator();
15
16 List<String> serverList = new ArrayList<String>();
17 while (iterator.hasNext())
18 {
19 String server = iterator.next();
20 int weight = serverMap.get(server);
21 for (int i = 0; i < weight; i++)
22 serverList.add(server);
23 }
24
25 String server = null;
26 synchronized (pos)
27 {
28 if (pos > keySet.size())
29 pos = 0;
30 server = serverList.get(pos);
31 pos ++;
32 }
33
34 return server;
35 }
36 }
关键是在获得服务器之前加入了下面的权重代码,如果权重大,在serverList里面的该服务就多,就能多分得几次请求。
while (iterator.hasNext())
18 {
19 String server = iterator.next();
20 int weight = serverMap.get(server);
21 for (int i = 0; i < weight; i++)
22 serverList.add(server);
23 }