一致哈希算法要求
节点数量的增加 要能够保证旧的数据能够按照老的算法 环形
减少映射关系的变动
所以不能安装数量来hash求余之类的操作
否者将会导致大量的节点数据失效和迁移
一致哈希算法设计标准
再不改边原有映射关系的前提下 将数据均匀分流的其他节点
(创建虚拟节点 再导流到下一个节点 但是会有雪崩的危险)
protected void setKetamaNodes(List<String> nodes) {
//为每个节点生成100个虚拟节点 应为nodes是list 所以可以重复 后面计算虚拟节点的时候 应该避免碰撞
final TreeMap<Long, String> newNodeMap = new TreeMap<Long, String>();
final Set<String> newNodeSet = new HashSet<String>();
final Map<String, Integer> nodeCounts = new TreeMap<String, Integer>();
//一百个虚拟节点
final int numReps = DEFAULT_REPETITIONS;
for (final String node : nodes) {
if (node == null) { throw new InvalidParameterException("Nodes may not be NULL."); }
//加权节点 其实就是相同节点生成多份虚拟节点 而生成虚拟节点主要根据node生成 那就给node再拼接一个数量就ok了
final int nodeCount = nodeCounts.containsKey(node) ? nodeCounts.get(node).intValue() + 1 : 1;
final String nodePlusCount = decorateWithCounter(node, nodeCount);
//添加相同节点的数量
nodeCounts.put(node, nodeCount);
//nodes转存到set里面 应为会有重复
newNodeSet.add(node);
//每个节点分配100个虚拟节点 除以4 是为了充分利用MD5生成的长度为16的字节数组 这个数组生成4个long 正好给一组虚拟节点作为key
*不直接遍历100个 然后取Md5的后4个字节 可能因为碰撞更小吧*
拆分4个一组 然后每组再生成4个key 最后再不发生key碰撞的情况下 每个节点的虚拟节点还是100
for (int i = 0; i < numReps / 4; i++) {
final byte[] digest = computeMd5(decorateWithCounter(nodePlusCount, i));
for (int h = 0; h < 4; h++) {
final Long k = ((long) (digest[3 + h * 4] & 0xFF) << 24)
| ((long) (digest[2 + h * 4] & 0xFF) << 16)
| ((long) (digest[1 + h * 4] & 0xFF) << 8)
| (digest[h * 4] & 0xFF);
newNodeMap.put(k, node);
}
}
}
//断言 小于1000 key碰撞 则抛异常 测试环境
assert newNodeSet.size() < 1000 ? newNodeMap.size() == numReps * nodes.size() : true : "Size: " + newNodeMap.size() + ", expected: " + (numReps * nodes.size());
data = new KetamaData(newNodeMap, newNodeSet);
}