不加虚拟节点的
加虚拟节点的
一致性哈希算法的优势
使用:hash(key)%容器,如果加入或者删除节点,会将所有key对应的服务器重新排序。
一致性哈希算法使用TreeMap来实现,当由节点加入或者删除时,只会影响一小部分。
原理
物理节点和虚拟节点
物理节点是物理机的地址,一个物理节点可以对应多个虚拟节点。
当一个物理节点退出系统时,其所有虚拟节点也会从哈希环中被移除。
虚拟节点的作用:
- 更均匀的数据分布
- 动态平衡负载,对于性能更强的物理节点,可以增加其虚拟节点的数量,让其承担更多的数据负载
通过TreeMap来实现一致性哈希算法的实践
虚拟节点:是通过key为虚拟节点的位置,而value为真实的物理节点来实现。
public class ConsistentHashing {
private final TreeMap<Long, String> circle = new TreeMap<>();
private final int numberOfReplicas;//每个物理节点的虚拟节点数量
public ConsistentHashing(int numberOfReplicas) {
this.numberOfReplicas = numberOfReplicas;
}
// 添加一个节点
public void addNode(String node) {
for (int i = 0; i < numberOfReplicas; i++) {
long hash = hash(node + i);
circle.put(hash, node);
}
}
// 移除一个节点
public void removeNode(String node) {
for (int i = 0; i < numberOfReplicas; i++) {
long hash = hash(node + i);
circle.remove(hash);
}
}
// 获取一个节点
public String getNode(String key) {
if (circle.isEmpty()) {
return null;
}
long hash = hash(key);
if (!circle.containsKey(hash)) {
SortedMap<Long, String> tailMap = circle.tailMap(hash);
hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
}
return circle.get(hash);
}
// 计算哈希值
private long hash(String key) {
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] bytes = md.digest(key.getBytes(StandardCharsets.UTF_8));
return ((long) (bytes[0] & 0xFF) << 56)
| ((long) (bytes[1] & 0xFF) << 48)
| ((long) (bytes[2] & 0xFF) << 40)
| ((long) (bytes[3] & 0xFF) << 32)
| ((long) (bytes[4] & 0xFF) << 24)
| ((long) (bytes[5] & 0xFF) << 16)
| ((long) (bytes[6] & 0xFF) << 8)
| ((long) (bytes[7] & 0xFF));
}
public static void main(String[] args) {
ConsistentHashing consistentHashing = new ConsistentHashing(3);
consistentHashing.addNode("NodeA");
consistentHashing.addNode("NodeB");
consistentHashing.addNode("NodeC");
System.out.println("Key1 is mapped to " + consistentHashing.getNode("Key1"));
System.out.println("Key2 is mapped to " + consistentHashing.getNode("Key2"));
System.out.println("Key3 is mapped to " + consistentHashing.getNode("Key3"));
consistentHashing.removeNode("NodeB");
System.out.println("After removing NodeB:");
System.out.println("Key1 is mapped to " + consistentHashing.getNode("Key1"));
System.out.println("Key2 is mapped to " + consistentHashing.getNode("Key2"));
System.out.println("Key3 is mapped to " + consistentHashing.getNode("Key3"));
}
}