负载均衡之一致性哈希环算法

前言

现在有一个场景:一个客户端去访问服务端,只能一直访问一台机器,因为有一些用户数据就存在该服务器上面,如果访问其他服务器的话,这个用户数据就丢了,如何用算法来解决这个问题?

一致性哈希环算法

base

在这里插入图片描述
这里的环结构是我们想象的、抽象出来的,最朴素的想法,就是给一个请求,用哈希函数等操作去计算出它对应到的服务器位置,然后分配即可。
客户端如何映射到哈希环上的节点呢?

比如有一个userId =1去请求:
计算了哈希值后落在了P1与P4之间,哈希环处理规则就是找到比它大的最近的那个节点,这里的哈希环是一个有序环,依次递增:P1< P2 <P3 <P4,那么比userId =1大的最近的就是P1,那么就会分配给P1这个服务器。
在这里插入图片描述
在Java中有哪个数据结构是可以满足这个需求的呢?完成自动排序(比如一个数字进到集合中后,该结构能够将新来的和之前的集合元素进行排序,这里采用的是TreeMap)

变种:穿插虚拟节点分担请求压力

思考一个问题:如果在某个高峰时间,所有的请求都打到了P1-P
4这个区域,那么将会全部击中P1这台服务器,P1承受很大流量,可能导致宕机,同时其他服务器可能比较空闲,这样并不合理,我们可以采用很多的虚拟节点,就可以在原有基础之上,中间穿插许多的虚拟节点,类似孙悟空拔了一根汗毛做分身,这样就可以把刚刚那一大堆流量给分散到不同服务器上,使得压力分散的很均匀、散列:
在这里插入图片描述

一致性哈希环的数据结构实现:TreeMap

TreeMap的底层采用的是红黑树,
在这里插入图片描述
在这里插入图片描述
这里的红黑树会对插入的数据完成自动排序,在TreeMap的api中有一个tailMap() 函数,输入一个fromKey,输出的是一个SortedMap有序Map:

 SortedMap<Integer,String> subMap = virtualNodes.tailMap(2);
 Integer nodeIndex = subMap.firstKey();//拿到第一个元素

能够得到2、3、4、5这几个索引的数据,String是它对应的value。
简单来说:能够通过tailMap() 找到2右边的子树(也就是大于等于2的子树),再用firstKey()拿到比他大的第一个元素。

这样就对应到了我们的一致性哈希环,可以拿到它最近的比他大的服务器节点

考虑边界:如果请求计算出比P4还要大,那么就直接返回P1即可(环状)

代码

//一致性hash 利用treemap实现
public class ConsistentHash {
    private static TreeMap<Integer,String> virtualNodes = new TreeMap();
    private static final int VIRTUAL_NODES = 160;//虚拟节点个数

    //预处理 哈希环
    static {
        //对每个真实节点添加虚拟节点 根据hash算法进行散列
        for(String ip: ServerIps.LIST) {
            for(int i = 0; i < VIRTUAL_NODES; i++) {
                int hash = getHash(ip+"VN"+i);
                virtualNodes.put(hash,ip);
            }
        }
    }

    public static String getServer(String clientInfo) {
        int hash = getHash(clientInfo);
        //得到大于该Hash值的子红黑树
        SortedMap<Integer,String> subMap = virtualNodes.tailMap(hash);
        //获取该子树最小元素
        Integer nodeIndex = subMap.firstKey();

        //没有大于该元素的子树 取整树的第一个元素
        if (nodeIndex == null) {
            nodeIndex = virtualNodes.firstKey();
        }
        return virtualNodes.get(nodeIndex);
    }


    //hash算法
    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 < 20; i++) {
            System.out.println(getServer("userId" + i));
        }
    }
}
//一致性hash 利用treemap实现
public class ConsistentHash {
    private static TreeMap<Integer,String> virtualNodes = new TreeMap();
    private static final int VIRTUAL_NODES = 160;//虚拟节点个数

    //预处理 哈希环
    static {
        //对每个真实节点添加虚拟节点 根据hash算法进行散列
        for(String ip: ServerIps.LIST) {
            for(int i = 0; i < VIRTUAL_NODES; i++) {
                int hash = getHash(ip+"VN"+i);
                virtualNodes.put(hash,ip);
            }
        }
    }

    public static String getServer(String clientInfo) {
        int hash = getHash(clientInfo);
        //得到大于该Hash值的子红黑树
        SortedMap<Integer,String> subMap = virtualNodes.tailMap(hash);
        //获取该子树最小元素
        Integer nodeIndex = subMap.firstKey();

        //没有大于该元素的子树 取整树的第一个元素
        if (nodeIndex == null) {
            nodeIndex = virtualNodes.firstKey();
        }
        return virtualNodes.get(nodeIndex);
    }


    //hash算法
    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 < 20; i++) {
            System.out.println(getServer("userId" + i));
        }
    }
}

在这里插入图片描述
可以看出运行结果是相对比较散列的,对于服务器来说压力就比较小了。
这里还要测试保证一下:对于同一个userId,每次去请求,都会到达同一个服务器上,这样才合理(每次运行,结果都一致即可)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值