负载均衡算法

随机算法

既然稍微了解了一下spring-cloud IRule的算法,那我就去找那些大佬稍微学习了一下基础的几个负载均衡算法,现在来做一个记录,可能写的不是很好,毕竟算法这个东西有一种只可意会不可言传的感觉。

先来一个入门级算法吧

普通随机算法

这个没啥好说的就只是简单随机一下就可以了。我们先编写一个ServerIps类来存放ip集合:

public class ServerIps {

    public static final List<String> IP_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"
    );

    /**
     *  key = ip
     *  value = 权重占比
     */
    public static final Map<String,Integer> IP_MAP = new HashMap<String,Integer>();

    static {
        IP_MAP.put("192.168.0.1",1);
        IP_MAP.put("192.168.0.2",8);
        IP_MAP.put("192.168.0.3",3);
        IP_MAP.put("192.168.0.4",6);
        IP_MAP.put("192.168.0.5",5);
        IP_MAP.put("192.168.0.6",5);
        IP_MAP.put("192.168.0.7",6);
        IP_MAP.put("192.168.0.8",7);
        IP_MAP.put("192.168.0.9",2);
        IP_MAP.put("192.168.0.10",9);
    }
}

新建一个RandomTest类

public class RandomTest {
/**
     * 随机算法
     * @return
     */
    public static String getRandomIp(){
        int ipAddres = new Random().nextInt(ServerIps.IP_LIST.size());
        return ServerIps.IP_LIST.get(ipAddres);
    }

    public static void main(String[] args) {
      for(int i=0;i<10;i++){
          System.out.println(getRandomIp());
      }
    }

运行一下main方法。简单是随机算法就完成了,是不是太简单了。不过呢,不要慌,这个只是最最最最简单的一个了,感觉都算不上算法,接下来我们稍微加一点难度吧。

权重随机算法

我们正式环境的服务器可不一定是每一台都一致的,总有性能好的和性能稍微差一点,所以这就会涉及到一个服务器权重,所以接下来我们做一下权重随机算法:
先讲一下这里的思路,如图:
在这里插入图片描述
画图这个真不会,所以就简单的意思意思了一下,我们还是继续来写代码吧。看一下代码就知道咋回事儿了:

public class RandomTest {

	 /**
     * 随机算法
     * @return
     */
    public static String getRandomIp(){
        int ipAddres = new Random().nextInt(ServerIps.IP_LIST.size());
        return ServerIps.IP_LIST.get(ipAddres);
    }
	/**
     * 加权随机算法
     * @return
     */
    public static String getWeightId(){

        //将ip占比转换为对象数组,用于循环计算权重总数
        Object[] ips = ServerIps.IP_MAP.values().toArray();

        int ipTotal = 0;//权重总数
        boolean flag=true;//用于判断权重是不是一样的

        for(int i=0;i<ips.length;i++){
            //计算权重总数
           Integer ipWeight = (Integer) ips[i];
           ipTotal += ipWeight;

           //判断所有ip权重是否一致
           if(flag && i>0 && !ipWeight.equals(ips[i-1])){
               flag = false;
           }
        }
        //权重不一致,就随机一个数来判断一下这个树落在哪里
        if(!flag){
            //产生一个随机数
            int ipAddres = new Random().nextInt(ipTotal);
            for(String ipKey : ServerIps.IP_MAP.keySet()){
                Integer ipValue = ServerIps.IP_MAP.get(ipKey);
                if(ipAddres<=ipValue){
                    return ipKey;
                }
                ipAddres = ipAddres - ipValue;
            }
        }
        //所有权重都一致,那就随机好啦
        return getRandomIp();
    }

    public static void main(String[] args) {
      for(int i=0;i<10;i++){
          System.out.println(getWeightId());
      }
    }

运行一下,其实少量数据也看不出啥结果,这个还是一个概率问题,需要大量数据来统计分析的,但你数据越大就越会发现某个Ip出现的概率绝对跟他的权重占比相近。

轮询算法

随机轮询算法

一样的,我们先来一个简单的轮询算法,怎么理解这个轮询呢,就是一个接着一个,跟学生在食堂打饭一样,一个接一个(千万别说插队,那个你可以假装不知道)。还是来撸代码吧

public class PollingAlgorithm {

    //定义一个全局参数,轮询依据
    private static Integer address = 0;

    /**
     * 简单轮询算法
     * @return
     */
    public static String getPollingIp(){
        String ip = null;
        //这里加一个锁,防止并发影响轮询结果
        synchronized (address){
            if(address>= ServerIps.IP_LIST.size()){
                address = 0;
            }
            ip = ServerIps.IP_LIST.get(address);
            address++;
        }
        return ip;
    }


    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            System.out.println(getPollingIp());
        }
    }

}

运行结果,不用猜也知道了…
在这里插入图片描述
是吧,一个接着一个,故意加了一个锁,就是怕你们来插队。

加权轮询算法

好了,我们接下来在来看一下权重轮询算法吧,这里大概讲一下意思就不画图了,跟上面个随机权重有点像,不过一个是随机一个是轮询而已。
我们需要一个计数器,每次使用后就加1,然后我们根据计数器来除以权重总数得到余数,然后判断余数落在哪个点上就返回这个点上对应的IP就好。代码如下:
新建一个Sequence类来当计数器:

public class Sequence {

    public static Integer num = 0;

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

改一下 PollingAlgorithm类:

public class PollingAlgorithm {

    //定义一个全局参数,轮询依据
    private static Integer address = 0;

    /**
     * 简单轮询算法
     * @return
     */
    public static String getPollingIp(){
        String ip = null;
        //这里加一个锁,防止并发影响轮询结果
        synchronized (address){
            if(address>= ServerIps.IP_LIST.size()){
                address = 0;
            }
            ip = ServerIps.IP_LIST.get(address);
            address++;
        }
        return ip;
    }

    public static String getWeightPollingIp(){
        //将ip占比转换为对象数组,用于循环计算权重总数
        Object[] ips = ServerIps.IP_MAP.values().toArray();

        int ipTotal = 0;//权重总数
        boolean flag=true;//用于判断权重是不是一样的

        for(int i=0;i<ips.length;i++){
            //计算权重总数
            Integer ipWeight = (Integer) ips[i];
            ipTotal += ipWeight;

            //判断所有ip权重是否一致
            if(flag && i>0 && !ipWeight.equals(ips[i-1])){
                flag = false;
            }
        }
        //取得计数器
        Integer sequenceNum = Sequence.getIncrement();

        //取余数
        Integer resultNum = sequenceNum%ipTotal;
        if(resultNum == 0){
            resultNum = ipTotal;
        }
        if(!flag){
            //循环判断当前这个点落在哪里
            for(String ipKey : ServerIps.IP_MAP.keySet()){
                Integer ipValue = ServerIps.IP_MAP.get(ipKey);
                if(resultNum <= ipValue){
                    return ipKey;
                }
                resultNum = resultNum - ipValue;
            }
        }
        //如果权重都相同,那就简单的轮询就行
       return getPollingIp();
    }

    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            System.out.println(getWeightPollingIp());
        }
    }

}

运行一下,这个就会根据你定义的权重来一个一个的走了。举一个例子,你定义了ABC三个IP地址,然后A占5,B占1,C占1那么这里打印的就会是
在这里插入图片描述
这个算是完成了吧,是不是很开心哈哈哈哈。但是呢,你有没有想过,一周七天,作为老大哥的A也不能连续上七天的班呀,那样超级累的,那我们能不能做成AABACAA,这样老大哥A也能中途出去玩一玩放松一下,毕竟劳逸结合才能做出更好的事情嘛。好吧,还能真能做出这种效果。接着看吧!

平滑加权轮询

首先我们来看一下大概的代码思想
在这里插入图片描述
好吧,不知道为啥这样做?或则不懂上面图片的意思?那就看这个吧:

现在是不是大概知道咋回事儿了?但是你不要问我为什么要这么操作。这个就像1+1为什么等于2不等于3。这个具体谁做出来的算法我也还没有找到根源,但是nginx的负载均衡算法好像就是这个,当然啦,比这个应该要复杂很多的。如果你还是不太懂呢,那就把下面代码多写几遍,这样你就知道是啥意思了。
新建一个IpWeight类

/**
 * ip就是保存ip咯
 * weight 保存 初始权重(固定不变的)
 * crrentWeight 当前权重,随时改变
 */
public class IpWeight {

    private String ip;

    private Integer weight;

    private Integer currentWeight;

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public Integer getWeight() {
        return weight;
    }

    public void setWeight(Integer weight) {
        this.weight = weight;
    }

    public Integer getCurrentWeight() {
        return currentWeight;
    }

    public void setCurrentWeight(Integer currentWeight) {
        this.currentWeight = currentWeight;
    }

    public IpWeight(String ip, Integer weight, Integer currentWeight) {
        this.ip = ip;
        this.weight = weight;
        this.currentWeight = currentWeight;
    }
}

再新建一个SmoothPollingAlgorithm类

public class SmoothPollingAlgorithm {

    private static Map<String,IpWeight> weightMap = new HashMap<String,IpWeight>();

    public static String getIp(){
        //将ip占比转换为对象数组,用于循环计算权重总数
        Object[] ips = ServerIps.IP_MAP.values().toArray();
        int ipTotal = 0;//权重总数

        for(int i=0;i<ips.length;i++){
            //计算权重总数
            Integer ipWeight = (Integer) ips[i];
            ipTotal += ipWeight;
        }
        //初始化 weightMap
        if(weightMap.isEmpty()){
            for(String ip:ServerIps.IP_MAP.keySet()){
                weightMap.put(ip,new IpWeight(ip,ServerIps.IP_MAP.get(ip),ServerIps.IP_MAP.get(ip)));
            }
        }

        //找出最大的IpWeight对象 根据currentWeight寻找
        IpWeight maxWeight = null;
        for (IpWeight weight:weightMap.values()){
            if(maxWeight == null || weight.getCurrentWeight()>maxWeight.getCurrentWeight()){
                maxWeight = weight;
            }
        }

        //将最大的IpWeight对象减去总权重
        maxWeight.setCurrentWeight(maxWeight.getCurrentWeight()- ipTotal);

        for (IpWeight weight:weightMap.values()){
            weight.setCurrentWeight(weight.getCurrentWeight()+weight.getWeight());
        }

        return maxWeight.getIp();
    }

    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            System.out.println(getIp());
        }
    }
}

运行一下,结果如下:
在这里插入图片描述
这个呢就叫做平滑加权轮询算法。是不是有点意思。

一致性Hash算法

一致性hash算法通过一个叫作一致性hash环的数据结构实现。这个环的起点是0,终点是2^32 - 1,并且起点与终点连接,环的中间的整数按逆时针分布,故这个环的整数分布范围是[0, 2^32-1]。
在这里插入图片描述
我们用这个能干啥?
服务器集群接收到一次请求调用时,可以根据请求的信息,比如客户端Ip地址,或则请求路径以及请求参数等信息进行哈希,可以得出一个哈希值,特点是对于相同的ip地址,或请求路径和参数哈希出来的值是一样的。只要再增加一个算法,能够把这个哈希值映射成一个服务端ip地址,就可以相同的请求(相同的ip地址,或请求路径和请求参数)落到同一台服务器上。
因为客户端的请求情况是无穷尽的(ip不同,地址不同,参数不同),所以对于哈希值也是无穷尽的,所以我们不可能把所有的哈希值都进行映射到服务器ip上,所以这里需要一个哈希环,如下:
在这里插入图片描述
但是这种情况要是挂掉一个就不公平了,比如ip2服务器挂了,然后ip3的压力就会增大,所以我们需要加入虚拟节点。大概如下:
在这里插入图片描述
就是添加很多虚拟节点,然后大家均分,这样就比较平均了,既重男也重女是吧!
那我们要来看一下代码了:

public class HashAlgorithm {

    //定义一个TreeMap,基于红黑树的一个map
    private static SortedMap<Integer, String> virtualNodes = new TreeMap<>();
    //每个真实节点对应的虚拟节点
    private static final int VIRTUAL_NODES = 160;
    //给每个真实节点添加虚拟节点
    static {
        for(String ip: ServerIps.IP_LIST){
            for(int i=0;i<VIRTUAL_NODES;i++){
                int hash = getHash(ip+"TE"+i);
                virtualNodes.put(hash,ip);
            }
        }
    }

    public static String getIp(String clint){
        //获取hash值
        int hash = getHash(clint);
        //得到大于这个hash值的map
        SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
        //大于该hash值的第一个元素位置
        Integer resultIndex = subMap.firstKey();
        //这是一个hash环,最后一个值就不能获取到了,需要给根节点
        if(resultIndex == null){
            resultIndex = virtualNodes.firstKey();
        }

        return virtualNodes.get(resultIndex);
    }

    private static int getHash(String str) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < str.length(); i++)
            hash = (hash ^ str.charAt(i)) * p;
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;
        //取出来的值为负数就取绝对值
        if (hash < 0)
            hash = Math.abs(hash);
        return hash;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(getIp("client" + i));
        }
    }
}

这里可能有一个TreeMap是一个比较生疏的技术点,这个还请大家各自自己去了解一下这里我就不讲解了,他是基于红黑树实现的。

好了 运行一下,其实你就会发现分散还比较平均(我前面拿了好多数据来测做了统计看的)。

前面几种方法主要目的是使服务器分配到的调用次数尽量均衡。但实际情况呢,还需要根据调用活跃度来进行计算的。

最小活跃数算法

这里呢稍微讲一下,就是首先获取服务器活跃度,拿到最小的活跃度的ip。如果只有一个,那么就直接调用这个ip。但是有多个咋办?那我们就来判断权重啦。权重大的优先,权重相同的就随机咯。哎,实在是不知道怎么说了,还是来看代码吧:

public class ServerIps {

    public static final List<String> IP_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"
    );

    /**
     *  key = ip
     *  value = 权重占比
     */
    public static final Map<String,Integer> IP_MAP = new HashMap<String,Integer>();

    static {
        IP_MAP.put("192.168.0.1",1);
        IP_MAP.put("192.168.0.2",8);
        IP_MAP.put("192.168.0.3",3);
        IP_MAP.put("192.168.0.4",6);
        IP_MAP.put("192.168.0.5",5);
        IP_MAP.put("192.168.0.6",5);
        IP_MAP.put("192.168.0.7",6);
        IP_MAP.put("192.168.0.8",7);
        IP_MAP.put("192.168.0.9",2);
        IP_MAP.put("192.168.0.10",9);
     /*   IP_MAP.put("A",5);
        IP_MAP.put("B",1);
        IP_MAP.put("C",1);*/
    }

    public static final Map<String,Integer>IP_ACTIVE = new HashMap<String,Integer>();

    static{
        IP_ACTIVE.put("192.168.0.1",2);
        IP_ACTIVE.put("192.168.0.2",0);
        IP_ACTIVE.put("192.168.0.3",1);
        IP_ACTIVE.put("192.168.0.4",3);
        IP_ACTIVE.put("192.168.0.5",0);
        IP_ACTIVE.put("192.168.0.6",1);
        IP_ACTIVE.put("192.168.0.7",4);
        IP_ACTIVE.put("192.168.0.8",2);
        IP_ACTIVE.put("192.168.0.9",7);
        IP_ACTIVE.put("192.168.0.10",0);
    }
}

public class MinActive {

    private static String getServer() {
        //找出数最小的服务器
        Optional<Integer> minValue =
                ServerIps.IP_ACTIVE.values().stream().min(Comparator.naturalOrder());
        if (minValue.isPresent()) {
            List<String> minActivityIps = new ArrayList<>();
            ServerIps.IP_ACTIVE.forEach((ip, activity) -> {
                if (activity.equals(minValue.get())) {
                    minActivityIps.add(ip);
                }
            });
            //最下活跃数有多个的时候,根据权重来选择,权重大的优先
            if (minActivityIps.size() > 1) {Map<String, Integer> weightList = new LinkedHashMap<String, Integer>
                    ();
                //过滤出对应ip和权重
                ServerIps.IP_MAP.forEach((ip, weight) -> {
                    if (minActivityIps.contains(ip)) {
                        weightList.put(ip, ServerIps.IP_MAP.get(ip));
                    }
                });
                int totalWeight = 0;//权重总数
                boolean sameWeight = true;//用于判断权重是不是一样的
                //将ip占比转换为对象数组,用于循环计算权重总数
                Object[] weights = weightList.values().toArray();
                for (int i = 0; i < weights.length; i++) {
                    Integer weight = (Integer) weights[i];
                    totalWeight += weight;
                    //判断所有ip权重是否一致
                    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 : weightList.keySet()) {
                        Integer value = weightList.get(ip);
                        if (randomPos < value) {
                            return ip;
                        }
                        randomPos = randomPos - value;
                    }
                }
                return (String) weightList.keySet().toArray()[new
                        java.util.Random().nextInt(weightList.size())];
            } else {
                return minActivityIps.get(0);
            }
        } else {
            return (String) ServerIps.IP_MAP.keySet().toArray()[new
                    java.util.Random().nextInt(ServerIps.IP_MAP.size())];
        }
    }

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

好吧,这里是有问题的,就是正常情况呢是在调用的时候将活跃度加1,返回成功后再减1的。但是今天实在是太累了,就忘记写了。不过只要知道有这个就行了,如果后面我还记得我一定来改掉。
运行结果就会发现192.168.0.10这个调用的次数稍微多一点咯。

好了,到此结束了。以上就是一些简单的负载均衡算法。希望能给你一丝帮助!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值