前言
在现在的大型互联网项目中,集群和分布式是常用的架构方式。在处理大用户量,高并发的情况中,分布式集群确实提供了一个比较可行的方法。在分布式集群中有一个很重要的概念,那就是”负载均衡”。在分布式集群中,一个服务可能要部署在多个不同服务器中,当大量请求到达的时候,请求被转发到集群的服务器中,这样便可以用多个集群服务器来分担单体服务器的压力。但是这里有一个不可忽略的概念,就是所谓的负载均衡,他是如何将大量的请求转发到不同的集群服务器中,以使得各个集群服务器平均分摊来自客户端的请求(这里的平均是指近似平均)?其实这就涉及到负载均衡算法。下面结合几个小例子来和大家一起学习一下这个负载均衡算法。
轮询实现负载均衡
所谓轮询实现负载均衡就是将客户端的请求按照集群服务器列表的顺序来一次分配,这种分配方式不管,集群服务器的配置和性能问题,只是单纯的按照服务器列表的顺序一次分配。下面简单模拟一下轮询方式实现负载均衡。
首先模拟一个集群服务器列表,map中的value是给每个集群服务器设置的权重,这在后面会用到
/**
*
* @ClassName: IpMap
* @Description: TODO(模拟集群的多台服务器)
* @author 爱琴孩
*/
public class IpMap
{
// 待路由的Ip列表,Key代表Ip,Value代表该Ip的权重
public static HashMap<String, Integer> serverWeightMap =
new HashMap<String, Integer>();
static{
serverWeightMap.put("192.168.1.100", 1);
serverWeightMap.put("192.168.1.101", 1);
serverWeightMap.put("192.168.1.102", 1);
serverWeightMap.put("192.168.1.103", 1);
serverWeightMap.put("192.168.1.104", 1);
serverWeightMap.put("192.168.1.105", 3);
serverWeightMap.put("192.168.1.106", 1);
serverWeightMap.put("192.168.1.107", 2);
serverWeightMap.put("192.168.1.108", 1);
serverWeightMap.put("192.168.1.109", 1);
serverWeightMap.put("192.168.1.110", 1);
}
}
然后再简单模拟轮询的具体操作
/**
* @ClassName: RoundRobin
* @Description: TODO(开启多线程中测试轮询)
* @author 爱琴孩
*/
public class RoundRobin implements Runnable
{
private static Integer pos = 0;
public static void getServer()
{ // 重建一个Map,避免服务器的上下线导致的并发问题
Map<String, Integer> serverMap =
new HashMap<String, Integer>();
IpMap map=new IpMap();
serverMap.putAll(IpMap.serverWeightMap);
// 取得Ip地址List
Set<String> keySet = serverMap.keySet();
ArrayList<String> keyList = new ArrayList<String>();
keyList.addAll(keySet);
String server = null;
synchronized (pos){
if (pos > keySet.size()){
pos = 0;
}
server = keyList.get(pos);
pos ++;
}
System.out.println("轮询转发的服务器"+server);
}
@Override
public void run() {
// TODO Auto-generated method stub
RoundRobin.getServer();
}
}
最后开启多线程来进行测试,测试结果如下
这种负载处理方式,是一般负载策略中的默认处理方式,但是刚才上面也说到,这种简单的轮询方式有一个弊端,就是他不会考虑各个服务器的具体配置和性能问题,有的服务器配置低,能负载的任务量少,有的服务器配置高,能同时处理的请求多。如果都是这样平均处理转发过来的请求,显然是不合理的。所以我们可以给每个集群服务器中加一个权重,然后再在加权重之后的服务器列表中采用轮询方式实现负载。这种方式可以简称为加权负载轮询,这种方式只需要将上面的方式做一个简单修改便可以,具体如下
/**
* @ClassName: RoundRobin
* @Description: TODO(开启多线程中测试加权轮询)
* @author 爱琴孩
*/
public class RoundRobin implements Runnable
{
private static Integer pos = 0;
public static void getServer()
{ // 重建一个Map,避免服务器的上下线导致的并发问题
Map<String, Integer> serverMap =
new HashMap<String, Integer>();
IpMap map=new IpMap();
serverMap.putAll(IpMap.serverWeightMap);
// 取得Ip地址List
Set<String> keySet = serverMap.keySet();
ArrayList<String> keyList = new ArrayList<String>();
Iterator<String> it=keySet.iterator();
while(it.hasNext()){
String serve=it.next();
Integer weight=serverMap.get(serve);
for(int i=0;i<weight;i++){
keyList.add(serve);
}
}
String server = null;
synchronized (pos){
if (pos > keySet.size()){
pos = 0;
}
server = keyList.get(pos);
pos ++;
}
System.out.println("轮询转发的服务器"+server);
}
@Override
public void run() {
// TODO Auto-generated method stub
RoundRobin.getServer();
}
}
测试结果如下
上面具体讲解的是简单轮询和加权轮询的方式。除了这两种方式,比较常用的还有IP散列法,还有随机法。下面将这两种例子结合在一起演示一下
随机法,IP散列法
/**
* @ClassName: LoadBalanceDemo
* @Description: TODO(简单模拟随机算法和hash散列算法)
* @author 爱琴孩
*/
public class LoadBalanceDemo {
public Map getMaps(){
Map<String,Integer> serverMap = new HashMap<String,Integer>(){{
put("192.168.1.100",1);
put("192.168.1.101",1);
put("192.168.1.102",1);
put("192.168.1.103",1);
put("192.168.1.104",1);
put("192.168.1.105",3);
put("192.168.1.106",1);
put("192.168.1.107",2);
put("192.168.1.108",1);
put("192.168.1.109",1);
put("192.168.1.110",9);
}};
return serverMap;
}
/**
*
* @Description: TODO(随机法)
* @author 爱琴孩
* @version V1.0
*/
@Test
public void random(){
Map<String,Integer> serverMap=getMaps();
List<String> keyList = new ArrayList<String>(serverMap.keySet());
Random random = new Random();
int idx = random.nextInt(keyList.size());
String server = keyList.get(idx);
System.out.println("简单随机得到的服务器"+server);
}
/**
*
* @Description: TODO(hash散列算法)
* @author 爱琴孩
* @version V1.0
*/
@Test
public void hash(){
Map<String,Integer> serverMap=getMaps();
List<String> keyList = new ArrayList<String>(serverMap.keySet());
String remoteIp = "192.168.222.100";
int hashCode = remoteIp.hashCode();
int idx = hashCode % keyList.size();
String server = keyList.get(Math.abs(idx));
System.out.println("hash散列得到的服务器是"+server);
}
}
上面两种方式分别对应于随机法和Ip散列法,对于随机法,在概率论中,当事件发生的基数很大的时候,这时候的随机事件的概率就近似于平均。也就是说,当请求量很大的时候,这种随机法和简单轮询的方式得到的结果是大致一样的。但是这种方式在请求量很大的情况下,即使和轮询方式相似,那么他也就有轮询方式的缺点,那就是不能兼顾每个集群服务器的各自性能和配置。当然随机法也可以进行加权处理,处理方式和上面的加权轮询类似,这里就不在演示了。
而对于ip散列这种方式,是根据由每个客户端的ip地址根据算法来算出一个具体值,然后再对服务器列表的个数进行取模运算,映射到对应的集群服务器。这种方式每个请求被转发到集群服务器是固定的,这样就可以避免因转发不确定性,导致session难以维护问题。
出来上面提到的这几种,我们还可能会根据最小连接数法,还有最短响应时间法。所谓最小连接数,就是根据当前集群服务器上已负载的请求数量,动态转发请求到对应的集群服务器,如果服务器上负载请求已经很多了,就不要在向该服务器上再转发请求。最短响应时间也是类似,如果一个服务器的对请求的响应时间很短很及时,那么代表这个服务器还能负载一些请求,如果一个服务器对请求的响应时间已经很长了,说明负载已经够了,,如果继续向这个服务器转发请求,这样请求肯定会被堵塞。
总结
上面简单介绍了,轮询负载,加权轮询,随机,加权随机,IP散列,最短响应时间,最小连接数等负载方法,当然还有其他的方法。但是具体使用哪一种方式,还是要结合具体的场景和实际情况来酌情使用,毕竟没有最好的方式,只有最合适的方式!