一致性哈希算法优势在哪?如何实现?

文章介绍了哈希算法的基本概念,用于文件指纹生成,以及普通哈希算法在数据映射中的问题,如在扩展时需要大量重分布数据。为解决这一问题,文章提出了使用一致性哈希算法,它通过对2^32取模并在虚拟环上分布节点来减少映射变动。文章还提供了一个简单的Java实现示例,展示了如何添加、删除节点和根据key选择节点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 普通哈希算法

1.1 简介Hash

哈希算法即散列算法,是一种从任意文件中创造小的数字「指纹」的方法。与指纹一样,散列算法就是一种以较短的信息来保证文件唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到逆向规律。因此,当原有文件发生改变时,其标志值也会发生改变,从而告诉文件使用者当前的文件已经不是你所需求的文件。
简单hash算法在实现上为:目标 target = hash(key)% 质数

1.2 普通Hash算法缺陷

举个例子:现在有一个数据库m1,要对一个user表进行水平拆分为user_0,user_1,user_2 三张表,如何进行合理的数据映射?

使用普通的hash算法的策略:
根据user的id进行hash运算,从而映射到对应的表,这时候 target = hash(key) % 3,即user_0 放的user的id都是 3的整数,同理user_1,user_2存放的user的id分别为 3余1,3余2…

如果随着业务的增加,user表要从原来的三个增为五个user表,上述表达式变为 target = hash(key) % 5, 这时所有记录的映射关系都会发生改变,导致我们需要对所有数据重新规整,放到对应的表中,这个任务量显然是巨大的…

2. 一致性哈希算法

一致性hash算法就是为了解决普通hash算法带来的痛点。

2.1 一致性hash算法原理

  1. 一致性Hash算法也是使用取模的方法,只是,刚才描述的取模法是对服务器的数量进行取模,而一致性Hash算法是对2^32取模,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,将各个服务器使用Hash进行计算,具体可以根据关键字进行哈希,这样每个记录就能确定其在哈希环上的位置。

可参考 https://blog.youkuaiyun.com/a745233700/article/details/120814088

2.2 一致性hash算法的代码实现

具体的项目实践,可参考这篇博客Sharding-JDBC 一致性hash算法数据库分片实践


import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.SortedMap;
import java.util.TreeMap;
 public class ConsistentHashing {
     // 一致性哈希算法中使用的哈希函数,默认使用FNV1_32_HASH
    private HashFunction hashFunction;
    // 虚拟节点个数
    private int numberOfReplicas;
    // 使用TreeMap来存储虚拟节点的哈希值和物理节点的映射关系
    private SortedMap<Integer, String> circle = new TreeMap<>();
     /**
     * 构造函数,初始化哈希函数和虚拟节点个数
     * @param hashFunction 哈希函数
     * @param numberOfReplicas 虚拟节点个数
     */
    public ConsistentHashing(HashFunction hashFunction, int numberOfReplicas) {
        this.hashFunction = hashFunction;
        this.numberOfReplicas = numberOfReplicas;
    }
     /**
     * 添加物理节点
     * @param node 物理节点名称
     */
    public void addNode(String node) {
        for (int i = 0; i < numberOfReplicas; i++) {
            // 对于每个物理节点,根据其名称和编号生成一个虚拟节点,并将其添加到虚拟节点环中
            int hashValue = hashFunction.getHash(node + i);
            circle.put(hashValue, node);
        }
    }
     /**
     * 删除物理节点
     * @param node 物理节点名称
     */
    public void removeNode(String node) {
        for (int i = 0; i < numberOfReplicas; i++) {
            // 对于每个物理节点,根据其名称和编号生成一个虚拟节点,并将其从虚拟节点环中删除
            int hashValue = hashFunction.getHash(node + i);
            circle.remove(hashValue);
        }
    }
     /**
     * 根据给定的key选择一个物理节点
     * @param key key
     * @return 物理节点名称
     */
    public String getNode(String key) {
        if (circle.isEmpty()) {
            return null;
        }
        int hashValue = hashFunction.getHash(key);
        // 如果环中不存在大于该hash值的节点,则直接返回环中最小的节点
        if (!circle.containsKey(hashValue)) {
            SortedMap<Integer, String> tailMap = circle.tailMap(hashValue);
            hashValue = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hashValue);
    }
     /**
     * 返回所有的物理节点
     * @return
     */
    public Collection<String> getAllNodes() {
        return new ArrayList<>(circle.values());
    }
     /**
     * 哈希函数接口
     */
    public interface HashFunction {
        int getHash(String key);
    }
     /**
     * FNV1_32_HASH算法,使用32位的FNV做hash,对于相同的字符串,不同的种子会产生不同的hash值
     */
    public static class FNV1_32_HASH implements HashFunction {
         public int getHash(String key) {
            final int p = 16777619;
            int hash = (int) 2166136261L;
            for (int i = 0; i < key.length(); i++) {
                hash = (hash ^ key.charAt(i)) * p;
            }
            hash += hash << 13;
            hash ^= hash >> 7;
            hash += hash << 3;
            hash ^= hash >> 17;
            return hash;
        }
    }
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值