想到之前十亿数据迁移的时候用到了哈希分桶,当时有同步验证一致性哈希的性能,这里记录下一致性哈希的实现方法。
哈希算法用的是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定义的顺序最小的键。

2689

被折叠的 条评论
为什么被折叠?



