zoukankan      html  css  js  c++  java
  • 哈希一致性算法以及代码实现

    当缓存很多时,搭建集群根据数据的key取hash存储到redis集群中。
    但是这种无法扩展。当增加redis节点或者减少节点时,hash值会变化。造成缓存失效。还可能引起缓存雪崩。使得请求压力都到了应用程序查库上面,使服务器崩溃。
    hash一致性算法是将计算hash的数据和redis服务器抽取出来。
    比如利用redis服务器的ip地址或者名称当做key。
    以0开始,2的32次方为终点,组成一个圆环。
    对redis服务器取hash,使其定位在圆环的某处。
    对数据取hash,当数据的hash与某个服务器hash一致时,表示此数据存储在此服务器上。
    当不一致时。根据此数据在圆环上的hash位置,顺时针到哪一个最近的redis服务器,数据就存储在那一台redis服务器中。
    这样就避免了加减redis节点造成的hash不一致问题。
    当增加或者减少redis节点时,只会对部分数据有影响,去查库。减轻服务器压力。
    另外,可能因为redis服务器hash有偏移,造成某一台压力比较大。可以制造虚拟节点。使得分布均匀一些。
     
    代码参考于:https://blog.csdn.net/suifeng629/article/details/81567777
     
     
    代码:
    import java.util.SortedMap;
    import java.util.TreeMap;
    /**
    * 不带虚拟节点的一致性Hash算法
    */
    public class ConsistentHashingWithoutVirtualNode {
    //待添加入Hash环的服务器列表
    private static String[] servers = { "192.168.0.0:111", "192.168.0.1:111",
    "192.168.0.2:111", "192.168.0.3:111", "192.168.0.4:111" };
    //key表示服务器的hash值,value表示服务器
    private static SortedMap<Integer, String> sortedMap = new TreeMap<Integer, String>();
    //程序初始化,将所有的服务器放入sortedMap中
    static {
    for (int i=0; i<servers.length; i++) {
    int hash = getHash(servers[i]);
    System.out.println("[" + servers[i] + "]加入集合中, 其Hash值为" + hash);
    sortedMap.put(hash, servers[i]);
    }
    System.out.println();
    }
    //得到应当路由到的结点
    private static String getServer(String key) {
    //得到该key的hash值
    int hash = getHash(key);
    //得到大于该Hash值的所有Map
    SortedMap<Integer, String> subMap = sortedMap.tailMap(hash);
    if(subMap.isEmpty()){
    //如果没有比该key的hash值大的,则从第一个node开始
    Integer i = sortedMap.firstKey();
    //返回对应的服务器
    return sortedMap.get(i);
    }else{
    //第一个Key就是顺时针过去离node最近的那个结点
    Integer i = subMap.firstKey();
    //返回对应的服务器
    return subMap.get(i);
    }
    }
     
    //使用FNV1_32_HASH算法计算服务器的Hash值,这里不使用重写hashCode的方法,最终效果没区别
    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) {
    String[] keys = {"太阳", "月亮", "星星"};
    for(int i=0; i<keys.length; i++)
    System.out.println("[" + keys[i] + "]的hash值为" + getHash(keys[i])
    + ", 被路由到结点[" + getServer(keys[i]) + "]");
    }
    }
    带虚拟节点的:
    /**
    * 带虚拟节点的一致性Hash算法
    */
    public class ConsistentHashingWithoutVirtualNode2 {
    //待添加入Hash环的服务器列表
    private static String[] servers = {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111",
    "192.168.0.3:111", "192.168.0.4:111"};
    //真实结点列表,考虑到服务器上线、下线的场景,即添加、删除的场景会比较频繁,这里使用LinkedList会更好
    private static List<String> realNodes = new LinkedList<String>();
    //虚拟节点,key表示虚拟节点的hash值,value表示虚拟节点的名称
    private static SortedMap<Integer, String> virtualNodes = new TreeMap<Integer, String>();
    //虚拟节点的数目,这里写死,为了演示需要,一个真实结点对应5个虚拟节点
    private static final int VIRTUAL_NODES = 5;
    static{
    //先把原始的服务器添加到真实结点列表中
    for(int i=0; i<servers.length; i++)
    realNodes.add(servers[i]);
    //再添加虚拟节点,遍历LinkedList使用foreach循环效率会比较高
    for (String str : realNodes){
    for(int i=0; i<VIRTUAL_NODES; i++){
    String virtualNodeName = str + "&&VN" + String.valueOf(i);
    int hash = getHash(virtualNodeName);
    System.out.println("虚拟节点[" + virtualNodeName + "]被添加, hash值为" + hash);
    virtualNodes.put(hash, virtualNodeName);
    }
    }
    System.out.println();
    }
    //使用FNV1_32_HASH算法计算服务器的Hash值,这里不使用重写hashCode的方法,最终效果没区别
    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;
    }
    //得到应当路由到的结点
    private static String getServer(String key){
    //得到该key的hash值
    int hash = getHash(key);
    // 得到大于该Hash值的所有Map
    SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
    String virtualNode;
    if(subMap.isEmpty()){
    //如果没有比该key的hash值大的,则从第一个node开始
    Integer i = virtualNodes.firstKey();
    //返回对应的服务器
    virtualNode = virtualNodes.get(i);
    }else{
    //第一个Key就是顺时针过去离node最近的那个结点
    Integer i = subMap.firstKey();
    //返回对应的服务器
    virtualNode = subMap.get(i);
    }
    //virtualNode虚拟节点名称要截取一下
    if(StringUtils.isNotBlank(virtualNode)){
    return virtualNode.substring(0, virtualNode.indexOf("&&"));
    }
    return null;
    }
    public static void main(String[] args){
    String[] keys = {"太阳", "月亮", "星星"};
    for(int i=0; i<keys.length; i++)
    System.out.println("[" + keys[i] + "]的hash值为" +
    getHash(keys[i]) + ", 被路由到结点[" + getServer(keys[i]) + "]");
    }
    }
  • 相关阅读:
    gif&png&jpg&webp
    设计点滴&css效果点滴
    backbone点滴
    js自己总结的小东西(打印出来方便学习)
    nodejs点滴
    js类型
    mongo学亮的分享
    npm package.json中的dependencies和devDependencies的区别
    161130、Dubbo+SpringMVC工程创建详解
    161129、详解5种跨域方式及其原理
  • 原文地址:https://www.cnblogs.com/fuguang/p/10814830.html
Copyright © 2011-2022 走看看