常见的简单负载均衡算法以及Java实现

https://blog.csdn.net/u010853261/article/details/54907644 原博客地址

读完本文你将知道: 
1. 什么是负载均衡? 
2. 负载均衡的几种简单实现: 
(1) 轮询法(Round Robin) 
(2)随机法(Random) 
(3)源地址Hash法(Hash) 
(4)加权轮询法(Weight Round Robin) 
(5)加权随机法(Weight Random) 
(6)最小连接数法(Least Connections)
1. 什么是负载均衡?

负载均衡(Load Balance),指由多台服务器以对称的形式组成的一个服务器集合,每台服务器都有等价的地位,都可以单独对外提供服务而无需其他服务器的辅助。通过某种负载分担技术,将外部发送来的请求均匀分配到对称结构中的一台服务器上,而接收到请求的服务器独立地回应客户的请求。负载均衡能够平均分配客户请求到服务器陈列,借此提供快速获取重要数据,解决大量并发访问服务问题,这种集群技术可以用最少的投资获得接近于大型主机的性能。

本文主要讲解将“外部发送来的请求均匀分配到对称结构中的某一台服务器上”的各种算法, 并以Java代码来模拟实现。

下面首先来以一个IP列表来模拟集群中的机器的IP集合。

package distributed;

import java.util.HashMap;

/**
 * Created by louyuting on 17/2/7.
 */
public class IPMap {

    public static HashMap<String, Integer> serverWeightMap = new HashMap<String, Integer>();

    static {
        //第一个参数是IP地址,第二个是权重.
        serverWeightMap.put("192.168.1.100", 1);
        serverWeightMap.put("192.168.1.101", 2);
        serverWeightMap.put("192.168.1.102", 3);
        serverWeightMap.put("192.168.1.103", 4);
        serverWeightMap.put("192.168.1.104", 3);
        serverWeightMap.put("192.168.1.105", 2);
        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", 4);
    }
}
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
1.轮训法

轮训法其实就是按照所有地址的序列,从前往后一次访问,实现代码如下:

package distributed;

import java.util.*;

/**
 * 轮训法
 */
public class RoundRobin {

    private static Integer pos=0;

    public static String getServerIP(){

        //重新在线程本地copy一份IPMap, 避免服务器上线下线导致的并发问题
        Map<String, Integer>  serverMap = new HashMap<String, Integer>();
        serverMap.putAll(IPMap.serverWeightMap);

        //取的IP地址的Set
        Set<String> ips = serverMap.keySet();

        List<String> iplist = new ArrayList<String>();
        iplist.addAll(ips);

        String server = null;

        synchronized (pos){
            if(pos > iplist.size())
                pos=0;

            server = iplist.get(pos);

            pos++;
        }
        return server;
    }

    /**
     *
     * @param args
     */
    public static void main(String[] args) {
        String result = null;

        for(int i=0; i<10; i++){
            result = getServerIP();
            System.out.println("load balance 的地址是:" + result);
        }
    }
}
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
48
49
由于serverWeightMap中的地址列表是动态的,随时可能有机器上线、下线或者宕机,因此为了避免可能出现的并发问题,方法内部要新建局部变量serverMap,现将serverMap中的内容复制到线程本地,以避免被多个线程修改。这样可能会引入新的问题,复制以后serverWeightMap的修改无法反映给serverMap,也就是说这一轮选择服务器的过程中,新增服务器或者下线服务器,负载均衡算法将无法获知。新增无所谓,如果有服务器下线或者宕机,那么可能会访问到不存在的地址。因此,服务调用端需要有相应的容错处理,比如重新发起一次server选择并调用。

对于当前轮询的位置变量pos,为了保证服务器选择的顺序性,需要在操作时对其加锁,使得同一时刻只能有一个线程可以修改pos的值,否则当pos变量被并发修改,则无法保证服务器选择的顺序性,甚至有可能导致iplist数组越界。

轮询法的优点在于:试图做到请求转移的绝对均衡。

轮询法的缺点在于:为了做到请求转移的绝对均衡,必须付出相当大的代价,因为为了保证pos变量修改的互斥性,需要引入重量级的悲观锁synchronized,这将会导致该段轮询代码的并发吞吐量发生明显的下降。

2.随机法

通过系统随机函数,根据后端服务器列表的大小值来随机选择其中一台进行访问。由概率统计理论可以得知,随着调用量的增大,其实际效果越来越接近于平均分配流量到每一台后端服务器,也就是轮询的效果。

实现代码如下:

package distributed;

import java.util.*;

/**
 * Created by louyuting on 17/2/7.
 * 随机法
 */
public class RandomIP {

    public static String getServerIP() {

        //重新在线程本地copy一份IPMap, 避免服务器上线下线导致的并发问题
        Map<String, Integer> serverMap = new HashMap<String, Integer>();
        serverMap.putAll(IPMap.serverWeightMap);

        //取的IP地址的Set
        Set<String> ips = serverMap.keySet();

        List<String> iplist = new ArrayList<String>();
        iplist.addAll(ips);

        String server = null;

        //获取IP的策略
        Random random = new Random();
        int pos = random.nextInt(iplist.size());
        return iplist.get(pos);
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        String result = null;
        for(int i=0; i<10; i++){
            result = getServerIP();
            System.out.println("load balance 的地址是:" + result);
        }
    }
}
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
整体代码思路和轮询法一致,先重建serverMap,再获取到server列表。在选取server的时候,通过Random的nextInt方法取0~iplist.size()区间的一个随机值,从而从服务器列表中随机获取到一台服务器地址进行返回。基于概率统计的理论,吞吐量越大,随机算法的效果越接近于轮询算法的效果。

3.源地址哈希法

源地址哈希的思想是获取客户端访问的IP地址值,通过哈希函数计算得到一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是要访问的服务器的序号。源地址哈希算法的代码实现大致如下:

package distributed;

import java.util.*;

/**
 * Created by louyuting on 17/2/7.
 * 源地址 hash法
 */
public class HashIP {

    public static String getServerIP() {

        //重新在线程本地copy一份IPMap, 避免服务器上线下线导致的并发问题
        Map<String, Integer> serverMap = new HashMap<String, Integer>();
        serverMap.putAll(IPMap.serverWeightMap);

        //取的IP地址的Set
        Set<String> ips = serverMap.keySet();

        List<String> iplist = new ArrayList<String>();
        iplist.addAll(ips);

        //获取IP的策略
        //获取远端请求的IP地址
        String remoteIP = "127.0.0.11";
        int hashCode = remoteIP.hashCode();
        hashCode = Math.abs(hashCode);//确保hash值是正数. 如果hash值是负数
        int ipSize = iplist.size();
        int pos = hashCode % ipSize;

        return iplist.get(pos);
    }


    /**
     * @param args
     */
    public static void main(String[] args) {
        String result = null;
        for(int i=0; i<10; i++){
            result = getServerIP();
            System.out.println("load balance 的地址是:" + result);
        }
    }
}
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
这个方法需要注意的一点是,当获取到远端ip的hash值之后,一定要取绝对值,因为如果是负数最后定位的索引就会是负数,导致数组越界。其实不同负载均衡算法的重点就在于路由算法,通过客户端的ip也就是remoteIp,取得它的Hash值,对服务器列表的大小取模,结果便是选用的服务器在服务器列表中的索引值。

源地址哈希法的优点在于:保证了相同客户端IP地址将会被哈希到同一台后端服务器,直到后端服务器列表变更。根据此特性可以在服务消费者与服务提供者之间建立有状态的session会话。

源地址哈希算法的缺点在于:除非集群中服务器的非常稳定,基本不会上下线,否则一旦有服务器上线、下线,那么通过源地址哈希算法路由到的服务器是服务器上线、下线前路由到的服务器的概率非常低,如果是session则取不到session,如果是缓存则可能引发”雪崩”。(缓存雪崩指:某些原因导致缓存全部失效,意味着所有的数据请求都会到达数据库,导致数据库请求暴增,导致数据库挂掉。)

4. 加权轮询法

不同的服务器可能机器配置和当前系统的负载并不相同,因此它们的抗压能力也不尽相同,给配置高、负载低的机器配置更高的权重,让其处理更多的请求,而低配置、高负载的机器,则给其分配较低的权重,降低其系统负载。加权轮询法可以很好地处理这一问题,并将请求顺序按照权重分配到后端。加权轮询法的代码实现大致如下:

package distributed;

import java.util.*;

/**
 * 加权轮训法
 */
public class WeightRoundRobin {

    private static Integer pos=0;

    public static String getServerIP(){

        //重新在线程本地copy一份IPMap, 避免服务器上线下线导致的并发问题
        Map<String, Integer>  serverMap = new HashMap<String, Integer>();
        serverMap.putAll(IPMap.serverWeightMap);

        //取的IP地址的Set
        Set<String> ips = serverMap.keySet();
        Iterator<String> iterator = ips.iterator();

        List<String> iplist = new ArrayList<String>();
        while (iterator.hasNext()){
            String server = iterator.next();
            int weight = serverMap.get(server);
            //按照权重来添加比例.
            for(int i=0; i<weight; i++){
                iplist.add(server);
            }
        }

        String server=null;
        synchronized (pos){
            if(pos > iplist.size())
                pos=0;

            server = iplist.get(pos);

            pos++;
        }
        return server;
    }

    /**
     *
     * @param args
     */
    public static void main(String[] args) {
        String result = null;

        for(int i=0; i<10; i++){
            result = getServerIP();
            System.out.println("load balance 的地址是:" + result);

        }
    }
}
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
48
49
50
51
52
53
54
55
56
57
与轮询法类似,只是在获取服务器地址之前增加了一段权重计算的代码,根据权重的大小,将地址重复地增加到服务器地址列表中,权重越大,该服务器每轮所获得的请求数量越多。

5. 加权随机

与加权轮询法类似,加权随机法也是根据后端服务器不同的配置和负载情况来配置不同的权重。不同的是,它是按照权重来随机选择服务器的,而不是顺序。加权随机法的代码实现如下:

package distributed;

import java.util.*;

/**
 * Created by louyuting on 17/2/7.
 * 随机法
 */
public class WeightRandom {

    public static String getServerIP() {

        //重新在线程本地copy一份IPMap, 避免服务器上线下线导致的并发问题
        Map<String, Integer>  serverMap = new HashMap<String, Integer>();
        serverMap.putAll(IPMap.serverWeightMap);

        //取的IP地址的Set
        Set<String> ips = serverMap.keySet();
        Iterator<String> iterator = ips.iterator();

        List<String> iplist = new ArrayList<String>();
        while (iterator.hasNext()){
            String server = iterator.next();
            int weight = serverMap.get(server);
            //按照权重来添加比例.
            for(int i=0; i<weight; i++){
                iplist.add(server);
            }
        }

        //获取IP的策略
        Random random = new Random();
        int pos = random.nextInt(iplist.size());
        return iplist.get(pos);
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        String result = null;
        for(int i=0; i<10; i++){
            result = getServerIP();
            System.out.println("load balance 的地址是:" + result);

        }
    }
}
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
48
这段代码相当于是随机法和加权轮询法的结合,比较好理解,就不解释了。

6. 最小连接数法

前面几种方法费尽心思来实现服务消费者请求次数分配的均衡,当然这么做是没错的,可以为后端的多台服务器平均分配工作量,最大程度地提高服务器的利用率,但是实际情况是否真的如此?实际情况中,请求次数的均衡真的能代表负载的均衡吗?这是一个值得思考的问题。

上面的问题,再换一个角度来说就是:以后端服务器的视角来观察系统的负载,而非请求发起方来观察。最小连接数法便属于此类。

最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它正是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能地提高后端服务器的利用效率,将负载合理地分流到每一台机器。由于最小连接数设计服务器连接数的汇总和感知,设计与实现较为繁琐,此处就不说它的实现了。

本文所有源码的github地址:https://github.com/leetcode-hust/leetcode/tree/master/louyuting/src/distributed
--------------------- 
作者:惜暮 
来源:CSDN 
原文:https://blog.csdn.net/u010853261/article/details/54907644 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/desert568/article/details/88107775