一致性哈希环的理论实现
前言
最近阅读社区代码时,发现了一段富有创造性的程序算法–一致性哈希环。也就是一致性哈希算法的具体实现,由一位微软工程师在提交社区代码时,笔者review到的,感觉代码实现严谨简洁,并且把一致性哈希环的特点全考虑到了,是一段很不错的算法程序。本文简单对其进行分析,解释。一致性哈希算法这里就不多介绍了,可点击笔者之前写过的文章一致性哈希算法。一致性哈希算法在分布式系统中有很多的应用场景,主要是为了解决数据出现“热点”问题。目前这段算法是用于为待写入数据选择目标集群位置的,目标集群会有很多个,而写入的文件数据只能选择其中1个集群。
一致性哈希环算法实现
/**
* Consistent hash ring to distribute items across nodes (locations). If we add
* or remove nodes, it minimizes the item migration.
* 一致性哈希环,分散化实体项的节点位置选择,减少因为节点的变更导致的其上所属实体项的迁移。
*/
public class ConsistentHashRing {
private static final String SEPERATOR = "/";
private static final String VIRTUAL_NODE_FORMAT = "%s" + SEPERATOR + "%d";
/** Hash ring 哈希环. */
private SortedMap<String, String> ring = new TreeMap<String, String>();
/** 虚拟节点信息 -> 节点数 映射信息 */
/** Entry -> num virtual nodes on ring. */
private Map<String, Integer> entryToVirtualNodes =
new HashMap<String, Integer>();
/** Synchronization 锁同步. */
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
public ConsistentHashRing(Set<String> locations) {
for (String location : locations) {
// 在环内添加位置信息
addLocation(location);
}
}
/**
* Add entry to consistent hash ring.
* 添加实体项到哈希环内
* @param location Node to add to the ring.
*/
public void addLocation(String location) {
// 虚拟出100个节点插入
addLocation(location, 100);
}
/**
* Add entry to consistent hash ring.
* 添加具体项到哈希环内
* @param location 需要添加的节点.
* @param numVirtualNodes 需要添加的虚拟节点数。
*/
public void addLocation(String location, int numVirtualNodes) {
writeLock.lock();
try {
// 更新虚拟节点列表信息
entryToVirtualNodes.put(location, numVirtualNodes);
for (int i = 0; i < numVirtualNodes; i++) {
// 得到虚拟节点名
String key = String.format(VIRTUAL_NODE_FORMAT, location, i);
// 取其哈希值
String hash = getHash(key);
// 加入到哈希环内
ring.put(hash, key);
}
} finally {
writeLock.unlock();
}
}
/**
* Remove specified entry from hash ring.
* 从哈希环内移除实体项
* @param location Node to remove from the ring.
*/
public void removeLocation(String location) {
writeLock.lock();
try {
// 移除给定节点位置,并获取其对应的虚拟节点数
Integer numVirtualNodes = entryToVirtualNodes.remove(location);
for (int i = 0; i < numVirtualNodes; i++) {
// 得到虚拟节点key,并从哈希环内移除
String key = String.format(VIRTUAL_NODE_FORMAT, location, i);
String hash = getHash(key);
ring.remove(hash);
}
} finally {
writeLock.unlock();
}
}
/**
* Return location (owner) of specified item. Owner is the next
* entry on the hash ring (with a hash value > hash value of item).
* 从哈希环内去得其最近的节点位置
* @param item Item to look for.
* @return The location of the item.
*/
public String getLocation(String item) {
readLock.lock();
try {
if (ring.isEmpty()) {
return null;
}
// 计算输入路径的哈希值
String hash = getHash(item);
// 如果哈希环内不恰好包含此节点
if (!ring.containsKey(hash)) {
// 将哈希环定位到大于此key的首个位置
SortedMap<String, String> tailMap = ring.tailMap(hash);
// 并得到第一个大于此key的项目的key,也就是距离最近的key
hash = tailMap.isEmpty() ? ring.firstKey() : tailMap.firstKey();
}
// 根据此key得到对应的虚拟节点信息
String virtualNode = ring.get(hash);
// 然后从虚拟节点信息中得到实际位置信息
int index = virtualNode.lastIndexOf(SEPERATOR);
if (index >= 0) {
return virtualNode.substring(0, index);
} else {
return virtualNode;
}
} finally {
readLock.unlock();
}
}
public String getHash(String key) {
return MD5Hash.digest(key).toString();
}
/**
* Get the locations in the ring.
* @return Set of locations in the ring.
*/
public Set<String> getLocations() {
return entryToVirtualNodes.keySet();
}
}