一致性哈希实现

想到之前十亿数据迁移的时候用到了哈希分桶,当时有同步验证一致性哈希的性能,这里记录下一致性哈希的实现方法。

哈希算法用的是MurMurHash3。详细性能评测网上也能查到,不在赘述。
在这里插入图片描述

实现一致性哈希算法主要是为了均衡的分桶,为了避免数据倾斜的问题,加入了虚拟节点。

一致性Hash算法引入了一个虚拟节点机制,即对每个服务器节点计算出多个hash值,它们都会映射到hash环上,映射到这些虚拟节点的对象key,最终会缓存在真实的节点上。
但需要注意一点,分配的虚拟节点个数越多,映射在hash环上才会越趋于均匀,节点太少的话很难看出效果
引入虚拟节点的同时也增加了新的问题,要做虚拟节点和真实节点间的映射,对象key->虚拟节点->实际节点之间的转换。
在这里插入图片描述

hash算法MurMurHash3的实现(引用了google的hash库):

public class MurMurHashUtils {

    /**
     * MurMurHash算法, 性能高, 碰撞率低
     * @param str String
     * @return Long
     */
    public static Long hash(String str) {
        HashFunction hashFunction = Hashing.murmur3_128();
        return hashFunction.hashString(str, StandardCharsets.UTF_8).asLong();
    }

    public static Integer hash32(String str) {
        HashFunction hashFunction = Hashing.murmur3_32();
        return hashFunction.hashString(str, StandardCharsets.UTF_8).asInt() & Integer.MAX_VALUE;
    }

    /**
     * Long转换成无符号长整型(C中数据类型)
     * Java的数据类型long与C语言中无符号长整型uint64_t有区别,导致Java输出版本存在负数
     * @param value long
     * @return Long
     */
    public static Long readUnsignedLong(long value) {
        if (value >= 0){
            return value;
        }
        return value & Long.MAX_VALUE;
    }

    /**
     * 返回无符号murmur hash值
     * @param key
     * @return
     */
    public static Long hashUnsigned(String key) {
        return readUnsignedLong(hash(key));
    }

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("CHONGQING");
        list.add("CHANGSHA");
        list.add("GUANGZHOU");
        list.add("SHENZHEN");
        list.add("001c4becd89f49f7b1c52fe4fcd54397");
        list.add("002b320c0e0347a8bcea7663192d8303");
        list.add("0035420515a24d9d875e6c4399bec8e3");
        list.add("00701f4c12364bedb626dd136cbc998b");
        list.add("008d028903da483fbee8d721b2e73934");
        list.add("00b9dce4ec2747c0aea6eb0494e38717");

        list.add("00001");
        list.add("00001000");
        list.add("0");

        for(int i = 0; i < 2000; i++){
            list.add(String.valueOf(i)); // 2000 个纯粹数字的测试
        }
        list.add("IwXTF4SWVs1gjGdszFOmFQ==450803mueIMZ6yyxYt5RbThp9LBA==18");
        list.add("IwXTF4SWVs1gjGdszFOmFQ==450803mueIMZ6yyxYt5RbThp9LBA==19");
        list.add("IwXTF4SWVs1gjGdszFOmFQ==450803mueIMZ6yyxYt5RbThp9LBA==0");
        list.add("IwXTF4SWVs1gjGdszFOmFQ==450803mueIMZ6yyxYt5RbThp9LBA==1");

        SortedSet<String> keys = new TreeSet<>();
        for (String key : list) {
            Long hash = hashUnsigned(key);
            System.out.println(key +  "---" + hash + "---" +  hash % 1000);
            keys.add(String.valueOf(hash % 1000));
        }
        System.out.println(keys);
        System.out.println("keys size:" + keys.size());
        System.out.println("list size:" + list.size());
    }


}

一致性哈希的实现:

/**
 * 一致性哈希算法 用于账单去重分桶
 * @author lightonyang
 * @date 2023/08/14
 */
public class ConsistentHashingUtils {
    private static final int numberOfVirtualNode = 200; // 每个物理节点对应的虚拟节点数量
    private static SortedMap<Long, String> circle = new TreeMap<>(); // 哈希环 哈希值对应节点

    //实际节点
    // private static List<String> node = Arrays.asList("1", "2", "3");

    //实际节点直接命名1...1000 循环生成
    static {
        for (int node=0;node<1000;node++) {
            for (int i = 0; i < numberOfVirtualNode; i++) {
                String virtualNode = node + "&Node" + i; //虚拟节点生成规则
                long hash = MurMurHashUtils.hashUnsigned(virtualNode);
                circle.put(hash, String.valueOf(node));
            }
        }
    }

    //生成虚拟节点
//    public void addNode(String node) {
//        for (int i = 0; i < numberOfVirtualNode; i++) {
//            String virtualNode = node + "&Node" + i; //虚拟节点生成规则
//            long hash = MurMurHashUtils.hashUnsigned(virtualNode);
//            circle.put(hash, node);
//        }
//    }


    public static Integer getNode(String key) {
        if (circle.isEmpty()) {
            return null;
        }

        long hash = MurMurHashUtils.hashUnsigned(key);

        // 查找大于或等于key哈希值的第一个虚拟节点
        SortedMap<Long, String> tailMap = circle.tailMap(hash);
        long nodeHash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();

        // 返回对应的物理节点
        return Integer.valueOf(circle.get(nodeHash));
    }

    public static void main(String[] args) {
        List<String> node = new ArrayList<>();
        for (int i=0;i<1000;i++){
            node.add(String.valueOf(i));
        }
        System.out.println(node.toString());
    }

}

注释比较详细,应该可以直接从注释看流程。主要就是:构建哈希环-》求key哈希和对应虚拟节点-》查距离Key最近的节点
其中关键点是用到了SortedMap的数据结构,以及SortMap的tailMap,firstKey的函数:

SortedMap会默认按键的自然顺序排序。
tailMap方法返回此映射的一部分,包括从指定键开始的所有键。
firstKey方法返回映射中的第一个键。这个键是按自然顺序或由构造SortedMap时提供的Comparator定义的顺序最小的键。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值