ref[move]: move hash to net package

This commit is contained in:
godotg
2023-09-02 19:17:15 +08:00
parent 00bb1e37a0
commit e158e92497
6 changed files with 4 additions and 105 deletions
@@ -22,7 +22,7 @@ import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.exception.RunException;
import com.zfoo.protocol.model.Pair;
import com.zfoo.protocol.registration.ProtocolModule;
import com.zfoo.util.math.ConsistentHash;
import com.zfoo.net.util.ConsistentHash;
import org.springframework.lang.Nullable;
import java.util.TreeMap;
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2020 The zfoo Authors
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.net.util;
import com.zfoo.protocol.model.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.TreeMap;
/**
* 带虚拟节点的一致性Hash算法,参考:http://www.zsythink.net/archives/1182
*
* @author godotg
* @version 3.0
*/
public class ConsistentHash<K, V> {
// 真实结点列表,考虑到服务器上线、下线的场景,即添加、删除的场景会比较频繁,这里使用LinkedList会更好
private List<Pair<K, V>> realNodes = new ArrayList<>();
// 虚拟节点,key表示虚拟节点的hash值,value表示虚拟节点的名称
private TreeMap<Integer, Pair<K, V>> virtualNodeTreeMap = new TreeMap<>();
// 虚拟节点的数目,数量越大约均匀
private int virtualNodes = 0;
public ConsistentHash(List<Pair<K, V>> realNodes, int virtualNodes) {
// 先把原始的服务器添加到真实结点列表中
this.realNodes.addAll(realNodes);
this.virtualNodes = virtualNodes;
// 初始化
// 再添加虚拟节点,遍历LinkedList使用foreach循环效率会比较高
for (var realNode : realNodes) {
addNode(realNode);
}
}
public void addNode(Pair<K, V> realNode) {
for (var i = 0; i < this.virtualNodes; i++) {
var virtualNode = realNode.getKey().toString() + "&&VN" + i;
var hash = HashUtils.fnvHash(virtualNode);
virtualNodeTreeMap.put(hash, realNode);
}
}
// 得到应当路由到的结点
public Pair<K, V> getRealNode(Object key) {
// 得到该key的hash值
var hash = HashUtils.fnvHash(key);
// 第一个Key就是顺时针过去离node最近的那个结点
var entry = virtualNodeTreeMap.ceilingEntry(hash);
if (Objects.isNull(entry)) {
// 如果没有比该key的hash值大的,则从第一个node开始
return virtualNodeTreeMap.firstEntry().getValue();
}
return entry.getValue();
}
public TreeMap<Integer, Pair<K, V>> getVirtualNodeTreeMap() {
return virtualNodeTreeMap;
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2020 The zfoo Authors
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.net.util;
/**
* 改进的hash算法虽然发布比较均匀,但是没有Java自带的hash算法速度快,所以需要知道自己需要什么
*
* @author godotg
* @version 3.0
*/
public abstract class HashUtils {
private static final int P = 16777619;
private static final int INIT_HASH = (int) 2166136261L;
/**
* 改进的32位FNV算法1
*
* @param data 数组
* @return hash结果
*/
public static int fnvHash(byte[] data) {
var hash = INIT_HASH;
for (byte b : data) {
hash = (hash ^ b) * P;
}
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return Math.abs(hash);
}
/**
* 改进的32位FNV算法1
*
* @param object 计算hash的对象,会调用toString方法
* @return hash结果
*/
public static int fnvHash(Object object) {
var hash = object.toString().chars().reduce(INIT_HASH, (left, right) -> (left ^ right) * P);
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return Math.abs(hash);
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2020 The zfoo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package com.zfoo.net.util;
import com.zfoo.protocol.model.Pair;
import com.zfoo.protocol.util.JsonUtils;
import com.zfoo.protocol.util.StringUtils;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;
/**
* @author godotg
* @version 3.0
*/
@Ignore
public class ConsistentHashTest {
//待添加入Hash环的服务器列表
private static final List<Pair<String, String>> servers = List.of(new Pair<>("192.168.0.0:111", "192.168.0.0:111")
, new Pair<>("192.168.0.1:111", "192.168.0.1:111"), new Pair<>("192.168.0.2:111", "192.168.0.2:111"));
private static final List<Pair<String, String>> nums = List.of(new Pair<>("1", "1"), new Pair<>("2", "2"), new Pair<>("3", "3"));
private static final List<Pair<String, String>> chars = List.of(new Pair<>("a", "a"), new Pair<>("b", "b"), new Pair<>("c", "c"));
@Test
public void consistentHashTest() {
test(servers);
System.out.println(StringUtils.MULTIPLE_HYPHENS);
test(nums);
System.out.println(StringUtils.MULTIPLE_HYPHENS);
test(chars);
System.out.println(StringUtils.MULTIPLE_HYPHENS);
}
public void test(List<Pair<String, String>> list) {
var realNodeMap = new HashMap<String, Integer>();
var hitNodeMap = new HashMap<Integer, String>();
list.forEach(it -> realNodeMap.put(it.getKey(), 0));
var consistentHash = new ConsistentHash<>(list, 300);
for (int i = 0; i < 100000; i++) {
var key = String.valueOf(i);
var realNode = consistentHash.getRealNode(key).getKey();
int nums = realNodeMap.get(realNode);
realNodeMap.put(realNode, ++nums);
hitNodeMap.put(i, realNode);
}
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 100000; j++) {
var key = String.valueOf(j);
var realNode = consistentHash.getRealNode(key).getKey();
Assert.assertEquals(realNode, hitNodeMap.get(j));
}
}
System.out.println(JsonUtils.object2String(realNodeMap));
}
@Test
public void testTreeMap() {
var treeMap = new TreeMap<Integer, String>();
treeMap.put(1, "a");
treeMap.put(100, "b");
treeMap.put(200, "c");
System.out.println(treeMap.ceilingEntry(8));
System.out.println(treeMap.ceilingEntry(450));
}
}