0
点赞
收藏
分享

微信扫一扫

扒一扒一致性Hash算法+Java版案例演示

穆熙沐 2022-01-15 阅读 33

title: 一致性Hash算法与Java实现
date: 2022/1/15 15:15:30


文章目录

一致性Hash算法的应用领域

  • Redis
  • Nginx
  • Dubbo
  • ElasticSearch
  • Hadoop
  • 分布式数据库
  • 其他分布式数据存储场景…

详解一致性Hash算法

image-20220115154223687

  • 对于Redis10W并发能力,大家应该也很清楚,但是如果并发量达到了30W呢?但就这一点,我们并不难解决,因为我们既然应用可以增加集群,那么缓存一样可以增加分布式的缓存集群:

image-20220115154459054

hash(key)%集群节点数

image-20220115160230710

  • 这种方案会存在一个问题,当我们由于业务需要,临时决定增加一个服务器来提高集群性能,会不会影响到原始的缓存数据呢?

image-20220115160622901

  • 经过简单计算,当原本的3台缓存服务器增加到4台后,我们将会有3/4的缓存数据将会失效,同理,如果原本缓存服务器就有99台,此时将其扩容至100,那么将会有99/100的缓存数据将会失效!这是在生产环境中不能接收的结果,大量的缓存失效,大量的请求直接访问数据库,这一幕不正是缓存雪崩所带来的后果吗?

一致性Hash算法

一致性Hash环

image-20220115162637322

  • 同样的问题,我们此时增加一台缓存服务器,会对原始缓存有什么影响?

image-20220115163252074

  • 很容易推导,受影响的数据范围和新增节点与上一个节点之间的差值有关,很容易就能对比出来,这点误差对比以前的纯Hash求余,已经小很多了

  • 对于第一个问题,答案是不能,比如上图中的3号,它只能分担来自2号服务器的压力,但是并不能分担01号的压力

  • 对于第二个问题,答案也是不能,因为图中只是展示出了一种理想的条件下,真实根据服务属性求出的hash值并不会严格的均匀

虚拟节点

image-20220115164936260

  • 此时再增加一个缓存服务器:

image-20220115165056017

  • 看图很明显得出结论:虚拟节点越多,分布便越均衡(第一个问题),新增的节点对原有节点的影响就越均衡(第二个问题),更进一步可以算出新增节点后,缓存失效的比例将只有1/nn表示原有服务器数)

手写一个一致性Hash算法

  • 物理节点
  • 虚拟节点
  • Hash算法,Java默认提供的HashCode方法得到的hash值散列度不是很好,而且会有负值(可以取绝对值),市面上的Hash算法也有很多,比如:CRC32_HASHFNV1_32_HASHKETAMA_HASHMemCache推荐的一致性Hash算法),咱们这里采用FNV1_32_HASH算法
private static int getHash(String string) {
    final int p = 16777619;
    int hash = (int) 2166136261L;
    for (int i = 0; i < string.length(); i++) {
        hash = (hash ^ string.charAt(i)) * p;
    }
    hash += hash << 13;
    hash ^= hash >> 7;
    hash += hash << 3;
    hash ^= hash >> 17;
    hash += hash << 5;
    return hash < 0 ? Math.abs(hash) : hash;
}
  • 一致性Hash环,我们顺时针看的话,其实就是一个排序的集合,而且往里加节点的时候,也要保证原有集合有序(可以延申出对于排序的数据集合,查找元素最快就是用二分查找),这时候采用什么数据结构呢?数组,对插入不太友好,链表,无法实现二分查找,树呢?答案就是红黑树,这时候就需要借助集合框架中的TreeMap,它底层就是采用红黑树实现!
  • 虚拟节点如何放到环上
  • 数据如何找到对应的虚拟节点
import lombok.Data;

import java.util.*;

/**
 * @author PengHuanZhi
 * @date 2022年01月15日 17:08
 */
@Data
public class ConsistenceHash {
    /**
     * 虚拟节点数量
     **/
    private int virtualNums = 100;
    
    /**
     * 物理节点
     **/
    private List<String> realNodes = new ArrayList<>();

    /**
     * 对应关系
     **/
    private Map<String, List<Integer>> real2VirtualNodeMap = new HashMap<>();

    /**
     * 一致性Hash环
     **/
    private SortedMap<Integer, String> hashRing = new TreeMap<>();

    /**
     * 加服务器
     *
     * @param node 真实节点
     **/
    public void addServer(String node) {
        realNodes.add(node);
        //虚拟出多少个虚拟节点
        String visualNode;
        List<Integer> virNodes = new ArrayList<>(virtualNums);
        real2VirtualNodeMap.put(node, virNodes);
        for (int i = 0; i < virtualNums; i++) {
            visualNode = node + "-" + i;
            //放到环上
            //1、求Hash值,由于Java默认提供的HashCode方法得到的hash值散列度不是很好,所以我们采用新的HashCode算法
            int hash = getHash(visualNode);
            //2、放到环上去
            hashRing.put(hash, node);
            //3、保存对应关系
            virNodes.add(hash);
        }
    }

    /**
     * 找到数据的存放节点
     *
     * @param virtualNode 虚拟节点key
     * @return 返回真实节点
     */
    public String getServer(String virtualNode) {
        int hashValue = getHash(virtualNode);
        SortedMap<Integer, String> subMap = hashRing.tailMap(hashValue);
        if (subMap.isEmpty()) {
            //数据应该放在最小的节点
            return hashRing.get(hashRing.firstKey());
        }
        return subMap.get(subMap.firstKey());
    }

    /**
     * 移除节点
     *
     * @param node 节点名称
     **/
    public void removeNode(String node) {
        realNodes.remove(node);
        real2VirtualNodeMap.remove(node);
        String visualNode;
        for (int i = 0; i < virtualNums; i++) {
            visualNode = node + "-" + i;
            int hash = getHash(visualNode);
            hashRing.remove(hash);
        }
    }

    /**
     * FNV1_32_HASH算法
     *
     * @param string key
     * @return int 返回Hash值
     **/
    private static int getHash(String string) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < string.length(); i++) {
            hash = (hash ^ string.charAt(i)) * p;
        }
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;
        return hash < 0 ? Math.abs(hash) : hash;
    }
}
举报

相关推荐

0 条评论