org.kaizen4j.common.algorithm.hash.ConsistentHashingShard Maven / Gradle / Ivy
package org.kaizen4j.common.algorithm.hash;
import com.google.common.base.Preconditions;
import org.apache.commons.collections4.CollectionUtils;
import org.kaizen4j.common.base.Symbols;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* 基于 MurmurHash 32 位的一致性 hash 算法。 默认虚拟节点数量为:100。
*/
public class ConsistentHashingShard {
private static Logger logger = LoggerFactory.getLogger(ConsistentHashingShard.class);
private static final int DEFAULT_SEED = 0x1234ABCD;
private static final int DEFAULT_VIRTUAL_NODE_NUM = 100;
private static final String VIRTUAL_NODE_PREFIX = "VN";
private int seed;
private int virtualNodeNum;
private SortedMap> virtualNodeMap;
public ConsistentHashingShard(List> realNodes) {
this(realNodes, DEFAULT_SEED, DEFAULT_VIRTUAL_NODE_NUM);
}
public ConsistentHashingShard(List> realNodes, int seed) {
this(realNodes, seed, DEFAULT_VIRTUAL_NODE_NUM);
}
public ConsistentHashingShard(List> realNodes, int seed, int virtualNodeNum) {
this.seed = seed;
this.virtualNodeNum = virtualNodeNum;
this.virtualNodeMap = new TreeMap<>();
initVirtualNode(realNodes);
}
private void initVirtualNode(List> realNodes) {
Preconditions.checkArgument(CollectionUtils.isNotEmpty(realNodes));
for (WeightedShard weightedShard : realNodes) {
// 加入权重,来控制虚拟节点的数量(virtualNodeNum * weight),从而控制节点被获取到的概率。
int nodeNum = virtualNodeNum * weightedShard.getWeight();
for (int n = 0; n < nodeNum; n++) {
String virtualName = getVirtualName(weightedShard.getServer(), n);
int hash = hash(virtualName);
virtualNodeMap.put(hash, weightedShard);
}
}
}
/**
* 生成虚拟节点的名称
*
* @param t 实现 toString 方法的服务节点对象
* @param n 虚拟节点的索引
* @return 虚拟节点的名称
*/
public String getVirtualName(T t, int n) {
return t.toString() + Symbols.COLON + VIRTUAL_NODE_PREFIX + n;
}
public T getShardInfo(String key) {
SortedMap> tailMap = virtualNodeMap.tailMap(hash(key));
if (0 == tailMap.size()) {
return virtualNodeMap.get(virtualNodeMap.firstKey()).getServer();
}
return tailMap.get(tailMap.firstKey()).getServer();
}
public int hash(String key) {
return hash(ByteBuffer.wrap(key.getBytes()), seed);
}
/**
* Hashes the bytes in a buffer from the current position to the limit.
*
* @param buf The bytes to hash.
* @param seed The seed for the hash.
* @return The 32 bit murmur hash of the bytes in the buffer.
*/
private int hash(ByteBuffer buf, int seed) {
// save byte order for later restoration
ByteOrder byteOrder = buf.order();
buf.order(ByteOrder.LITTLE_ENDIAN);
int m = 0x5bd1e995;
int r = 24;
int h = seed ^ buf.remaining();
while (buf.remaining() >= 4) {
int k = buf.getInt();
k *= m;
k ^= k >>> r;
k *= m;
h *= m;
h ^= k;
}
if (buf.remaining() > 0) {
ByteBuffer finish = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
// for big-endian version, use this first:
if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) {
finish.position(4 - buf.remaining());
}
finish.put(buf).rewind();
h ^= finish.getInt();
h *= m;
}
h ^= h >>> 13;
h *= m;
h ^= h >>> 15;
buf.order(byteOrder);
if (h < 0) {
h = Math.abs(h);
}
return h;
}
}