微服务架构专题六:负载均衡算法 (一)随机和轮询

负载均衡算法

常见的负载均衡算法有:随机算法、加权轮询、一致性hash、最小活跃数算法

算法前提条件:

定义一个服务器列表,没个负载均衡的算法会从中挑出一个服务器作为算法的结果。

public class ServerIps {
    public static final List<String> LIST = Arrays.asList(
            "192.168.0.1",
            "192.168.0.2",
            "192.168.0.3",
            "192.168.0.4",
            "192.168.0.5",
            "192.168.0.6",
            "192.168.0.7",
            "192.168.0.8",
            "192.168.0.9",
            "192.168.0.10"
    );
}

一、随机算法-RandomLoadBalance

1、先来个最简单的实现。

/**
 * 随机算法
 */
public class Random {
    public static String getServer() {
        // 生成一个随机数作为list的下标值
        java.util.Random random = new java.util.Random();
        int randomPos = random.nextInt(ServerIps.LIST.size());
        return ServerIps.LIST.get(randomPos);
    }
    public static void main(String[] args) {
        //连续调用10次
        for (int i=0; i<10; i++) {
            System.out.println(getServer());
        }
    }
}

运行结果:
微服务架构专题六:负载均衡算法 (一)随机和轮询_第1张图片

当调用次数比较少时,Random产生的随机数可能会比较集中,此时多数请求会落到同一台服务器上,只有在经过多次请求后,才能使调用请求进行“均衡”分配。调用量少这一点并没有什么关系,负载均衡机制不正是为了应对请求量多的情况吗,所以随机算法也是用的比较多的一种算法。

但是,上面的随机算法适用于每台机器的性能差不多的时候,实际上,生产中可能某些机器的性能更高一点,它可以处理更多的请求,所以,我们可以对每台服务器设置一个权重。

在ServerIps类中增加服务器权重对应关系MAP,权重之和为50:

public class ServerIps {
    public static final List<String> LIST = Arrays.asList(
            "192.168.0.1",
            "192.168.0.2",
            "192.168.0.3",
            "192.168.0.4",
            "192.168.0.5",
            "192.168.0.6",
            "192.168.0.7",
            "192.168.0.8",
            "192.168.0.9",
            "192.168.0.10"
    );
    public static final Map<String, Integer> WEIGHT_LIST = new HashMap<String, Integer>();
    static {
        // 权重之和为50
        WEIGHT_LIST.put("192.168.0.1", 1);
        WEIGHT_LIST.put("192.168.0.2", 8);
        WEIGHT_LIST.put("192.168.0.3", 3);
        WEIGHT_LIST.put("192.168.0.4", 6);
        WEIGHT_LIST.put("192.168.0.5", 5);
        WEIGHT_LIST.put("192.168.0.6", 5);
        WEIGHT_LIST.put("192.168.0.7", 4);
        WEIGHT_LIST.put("192.168.0.8", 7);
        WEIGHT_LIST.put("192.168.0.9", 2);
        WEIGHT_LIST.put("192.168.0.10", 9);
    }
}

那么现在的随机算法应该要改成权重随机算法,当调用量比较多的时候,服务器使用的分布应该近似对应权重的分布。

二、权重随机算法

简单的实现思路时,把每个服务器按照它所对应的服务器进行复制,具体看代码更加容易理解。

  • 1、简单权重随机算法
/**
 * 简单的权重随机算法
 */
public class WeightRandom {
    public static String getServer() {
        // 生成一个随机数作为listd的下标志
        List<String> ips = new ArrayList<String>();
        for (String ip : ServerIps.WEIGHT_LIST.keySet()) {
            Integer weight = ServerIps.WEIGHT_LIST.get(ip);
            // 按权重进行复制
            for (int i=0; i<weight; i++) {
                ips.add(ip);
            }
        }
        java.util.Random random = new java.util.Random();
        int randomPos = random.nextInt(ips.size());
        return ips.get(randomPos);
    }
    public static void main(String[] args) {
        // 连续调用10次
        for (int i=0; i<10; i++) {
            System.out.println(getServer());
        }
    }
}

这种实现方法在遇到权重之和特别大的时候就会比较消耗内存,因为需要对IP地址进行复杂,权重之和越大你那么上文中的ips就需要越多的内存,下面介绍另外一种实现思路。

  • 2、权重随机算法 — 实现思路二
    微服务架构专题六:负载均衡算法 (一)随机和轮询_第2张图片
    接下来通过随机数生成器生成一个范围在[0,10)至今的随机数,然后计算这个随机数会落到哪个区间上,即可知道返回的是哪个服务器。比如随机数为3,那么就返回A服务器。
    权重越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成的数字就会有更大的概率落到此区间内。只要随机数生成器产生的随机数分别性很好,在经过多次选择后,每个服务器被选择的次数比例接近其权重比例。

假设现在随机数offset=7:
1、offset<5 is false,所以不再[0,5)区间,将offset=offset-5(offset=2);
2、offset<3 is true,所以处于[5,8)区间,所以应该选用B服务器。

实现如下:

/**
 * 权重随机算法2
 */
public class WeightRandomV2 {
    public static String getServer() {
        int totalWeight = 0;
        boolean sameWeight = true; // 如果所以权重都相等,那么随机一个ip就好了
        Object[] weights = ServerIps.WEIGHT_LIST.values().toArray();
        for (int i = 0; i < weights.length; i++) {
            Integer weight = (Integer) weights[i];
            totalWeight += weight;
            if (sameWeight && i > 0 && !weight.equals(weights[i - 1])) {
                sameWeight = false;
            }
        }
        java.util.Random random = new java.util.Random();
        int randomPos = random.nextInt(totalWeight);
        if (!sameWeight) {
            for (String ip : ServerIps.WEIGHT_LIST.keySet()) {
                Integer value = ServerIps.WEIGHT_LIST.get(ip);
                if (randomPos < value) {
                    return ip;
                }
                randomPos = randomPos - value;
            }
        }
        return (String) ServerIps.WEIGHT_LIST.keySet().toArray()[new
                java.util.Random().nextInt(ServerIps.WEIGHT_LIST.size())];
    }
    public static void main(String[] args) {
        // 连续调用10次
        for (int i = 0; i < 10; i++) {
            System.out.println(getServer());
        }
    }
}

三、轮询算法-RoundRobinLoadbalance

  • 1、简单的轮询算法
/**
 * 简单轮询算法
 */
public class RoundRobin {

    //当前循环的位置
    private static Integer pos = 0;
    public static String getServer() {
        String ip = null;
        // pos同步
        synchronized (pos) {
            if (pos >= ServerIps.LIST.size()) {
                pos = 0;
            }
            ip = ServerIps.LIST.get(pos);
            pos++;
        }
        return ip;
    }
    public static void main(String[] args) {
        //连续调用10次
        for (int i = 0; i < 10; i++) {
            System.out.println(getServer());
        }
    }
}

运行结果:
微服务架构专题六:负载均衡算法 (一)随机和轮询_第3张图片

  • 2、权重轮询算法
    其中一种实现方法就是复制法,和简单的随机算法一样,这里就不演示了!

另一种算法就是:调用编号,比如第1次调用为1,第2次调用为2,第100次调用为100,调用编号是递增的,所以我们可以根据这个调用编号推算出服务器。

Servers=[A,B,C] 对应权重 Weights = [2,5,1] 那么权重和为8;
比如调用10次,其调用顺序为AABBBBBCAA
调用编号会越来越大,而服务器是固定的,所以需要把调用编号“缩小”,这里对调用编号进行取余,除数为总权重和,比如:
1、1号调用,1%8=1;
2、2号调用,2%8=2;
2、3号调用,3%8=3;
4、8号调用,8%8=0;
5、9号调用,9%8=1;
6、100号调用,100%8=4;

我们发现调用编号可以被缩小为0-7之间的8个数字,问题是怎么根据这8个数字找到对应的服务器咧?和我们随机算法类似,这里也可以把权重想象为一个坐标轴"0-----2----7—8"。
微服务架构专题六:负载均衡算法 (一)随机和轮询_第4张图片

实现:

public class ServerIps {
c static final Map<String, Integer> WEIGHT_LIST = new HashMap<String, Integer>();
    static {
        // 权重之和为50
//        WEIGHT_LIST.put("192.168.0.1", 1);
//        WEIGHT_LIST.put("192.168.0.2", 8);
//        WEIGHT_LIST.put("192.168.0.3", 3);
//        WEIGHT_LIST.put("192.168.0.4", 6);
//        WEIGHT_LIST.put("192.168.0.5", 5);
//        WEIGHT_LIST.put("192.168.0.6", 5);
//        WEIGHT_LIST.put("192.168.0.7", 4);
//        WEIGHT_LIST.put("192.168.0.8", 7);
//        WEIGHT_LIST.put("192.168.0.9", 2);
//        WEIGHT_LIST.put("192.168.0.10", 9);
        WEIGHT_LIST.put("A", 2);
        WEIGHT_LIST.put("B", 5);
        WEIGHT_LIST.put("C", 1);
    }
}

/**
 * 权重加轮询算法
 */
public class WeightRoundRobin {
    public static Integer num = 0;
    public static String getServer() {
        int totalWeight = 0;
        boolean sameWeight = true; // 如果所以权重都相等,那么随机一个ip就好了
        Object[] weights = ServerIps.WEIGHT_LIST.values().toArray();
        for (int i = 0; i < weights.length; i++) {
            Integer weight = (Integer) weights[i];
            totalWeight += weight;
            if (sameWeight && i > 0 && !weight.equals(weights[i - 1])) {
                sameWeight = false;
            }
        }
        Integer sequenceNum = getAndIncrement();
        Integer offset = sequenceNum % totalWeight;
        offset = offset == 0 ? totalWeight : offset;
        if (!sameWeight) {
            for (String ip : ServerIps.WEIGHT_LIST.keySet()) {
                Integer weight = ServerIps.WEIGHT_LIST.get(ip);
                if (offset <= weight) {
                    return ip;
                }
                offset = offset - weight;
            }
        }
        return null;
    }

    public static Integer getAndIncrement() {
        return ++num;
    }

    public static void main(String[] args) {
        连续调用10次
        for (int i = 0; i < 10; i++) {
            System.out.println(getServer());
        }
    }
}

运行结果:
微服务架构专题六:负载均衡算法 (一)随机和轮询_第5张图片

但是这种算法有一个缺点:一台服务器的权重特别大的时候,他需要连续的处理请求,但是实际上我们想达到的效果是对于100次请求,只要100*8%50=16次就够了,这16次不一定要连续的访问,比如假设我们有三台服务器Servers=[A,B,C] 对应权重 Weights = [5,1,1] 那么权重和为7,
那么上述这个算法的结果就是:AAAAABC,那么如果能够是这么一个结果呢:AABACAA,把B和C平均插入到5个A中间,这样是比较均衡的了。

  • 3、平滑加权轮询

我们这里可以改成平滑加权轮询,先讲一下思路:
在这里插入图片描述
下列表中,比如
请求编号1中,初始值为[5, 1, 1],数组中最大的值5,那么选择结果为A,选择后最大的值5减去权重和7,变成了[-2, 1, 1];

请求编号2来的时候,[-2, 1, 1]加上[5, 1, 1],那么就变成编号2中的初始值为[3, 2, 2],数组中最大的值3,那么选择结果为A,最大的值3减去权重和7,变成了[-4, 2, 2];

请求编号3来的时候,[-4, 2, 2]加上[5, 1, 1],那么就变成编号2中的初始值为[1, 3, 3],数组中最大的值3,那么选择结果为B,最大的值3减去权重和7,变成了[1, -4, 3];
以此类推。。。。。。。。
微服务架构专题六:负载均衡算法 (一)随机和轮询_第6张图片

实现:
ServerIps为:

    public static final Map<String, Integer> WEIGHT_LIST = new HashMap<String, Integer>();
    static {
        WEIGHT_LIST.put("A", 5);
        WEIGHT_LIST.put("B", 1);
        WEIGHT_LIST.put("C", 1);
    }
}

/**
 * 权重平滑加轮询算法
 */
public class WeightRoundRobinV2 {
    private static Map<String, Weight> weightMap = new HashMap<String, Weight>();
    public static String getServer() {
        // java8
        int totalWeight = ServerIps.WEIGHT_LIST.values().stream().reduce(0, (w1, w2) -> w1+w2);
        // 初始化weightMap,初始值currentWeight复制为weight
        if (weightMap.isEmpty()) {
            ServerIps.WEIGHT_LIST.forEach((key, value) -> {
                weightMap.put(key, new Weight(key, value, value));
            });
        }
        // 找出currentWeight最大值
        Weight maxCurrentWeight = null;
        for (Weight weight : weightMap.values()) {
            if (maxCurrentWeight == null || weight.getCurrentWeight() >
                    maxCurrentWeight.getCurrentWeight()) {
                maxCurrentWeight = weight;
            }
        }
        // 将maxCurrentWeight减去总权重和
        maxCurrentWeight.setCurrentWeight(maxCurrentWeight.getCurrentWeight() -
                totalWeight);
        // 所有的ip的currentWeight统一加上权重
        for (Weight weight : weightMap.values()) {
            weight.setCurrentWeight(weight.getCurrentWeight() + weight.getWeight());
        }
        // 返回maxCurrentWeight所对应的ip
        return maxCurrentWeight.getIp();
    }
    public static void main(String[] args) {
        //连续调用10次
        for (int i = 0; i < 10; i++) {
            System.out.println(getServer());
        }
    }
}

运行结果:
微服务架构专题六:负载均衡算法 (一)随机和轮询_第7张图片

以上均为鲁班学院学习资料,欢迎大家报班学习,真心推荐!

你可能感兴趣的:(算法,微服务,算法,java)