一致性哈希
先构造一个长度为2^32的一致性Hash环,根据服务器节点地址(ip+port)的Hash值将服务器节点映射到这个Hash环上,然后根据用户请求的Key值(用户ID)计算得到其Hash值,接着在Hash环上按顺时针或逆时针查找距离这个Key值的Hash值最近的服务器节点,由该服务器节点处理此次用户请求。如图:

一致性哈希是对普通哈希的改进,有效的解决了稳定性的问题。当服务器节点加入或退出时,只影响该节点在哈希环上顺时针相邻的后继节点。即当加入一个服务器节点时,原来分配到后继节点的一部分请求,重新分配给新加入的服务器节点;当退出一个服务器节点时,原来分配到该节点的请求,全部重新分配到后继节点。
一致性哈希解决了稳定性问题,但是又产生了负载不均衡问题,或者叫热点问题:当某个服务器节点或某几个服务器节点存在热点资源,这几个服务器节点就会处理大量的用户请求,其他服务器只处理很少的用户请求,这就产生了负载不均衡问题。
另外,当某个服务器节点崩溃退出,就会使该节点的后继节点负载增大。如果后继节点承受不住崩溃,就会传递给后继节点的后继节点,产生雪崩效应。
有两种解决方案:
有限负载的一致性哈希
根据当前负载情况对每个服务器节点设置一个最大请求负载值,在一致性哈希环中进行查找时将跳过达到最大负载限制的服务器节点,以此类推,这样把过载的请求转移到其他服务器节点上来解决热点和不均衡问题。如图:

服务器节点Server3正在处理两个用户请求,已经达到了最大负载限制数。所以第四个用户请求到来时,直接跳过Server3,分配Server4来处理请求。
虚拟节点的一致性哈希
将物理服务器节点分成一定数量的虚拟服务器节点,再将这些虚拟节点映射到哈希环上,由于这些虚拟节点分散到哈希环上,因此很大程度上解决了负载不均衡的问题。如图:

将物理服务器节点Server1和Sever2分别分解成3个虚拟节点,即一共6个虚拟节点映射到哈希环上,有效分解了物理服务器的请求负载。相应的当一个物理节点加入或退出,就对应到哈希环上的多个虚拟节点加入或退出,因此会导致用户请求的重新哈希或数据的迁移会更复杂。
哈希算法
- MurmurHash 算法:运算性能高,碰撞率低,Hadoop、Nginx、Redis、Memcached,Cassandra等开源项目都在使用这种算法。
- FNV 算法:FNV 能快速 hash 大量数据并保持较小的冲突率,它的高度分散使它适用于 hash 一些非常相近的字符串,比如 URL,IP 地址等。
- Ketama 算法:是由Last.fm的程序员实现的并得到了广泛的应用。
代码实现:
// 构造哈希环
public TreeMap<Integer, Node> buildConsistentHashRing(Map<String, Node> servers) {
TreeMap<Integer, Node> hashRing = new TreeMap<Integer, Node>();
for (Node node : servers.values()) {
hashRing.put(getHashCode(node.getNodeAddress()), node);
}
return hashRing;
}
// 构造带虚拟节点的哈希环
// helper.V_NODE_SIZE是虚拟节点的数量,helper.V_NODE_SUFFIX是虚拟节点的前缀
public TreeMap<Integer, Node> buildConsistentHashRing(Map<String, Node> servers) {
TreeMap<Integer, Node> hashRing = new TreeMap<Integer, Node>();
for (Node node : servers.values()) {
for (int i=0; i<helper.V_NODE_SIZE; i++) {
hashRing.put(getHashCode(node.getNodeAddress()+helper.V_NODE_SUFFIX+i), node);
}
}
return hashRing;
}
// 根据请求key的哈希值,在哈希环上寻找节点。
public Node locate(Map<String, Node> servers, int keyHashCode) {
TreeMap<Integer, Node> hashRing = buildConsistentHashRing(servers);
// 向右找到第一个key
Map.Entry<Integer, Node> locateEntry = hashRing.ceilingEntry(keyHashCode);
if (locateEntry == null) {
locateEntry = hashRing.firstEntry();
}
return locateEntry.getValue();
}
总结
一致性哈希解决了普通哈希的稳定性问题,但是又同时引入了负载不均衡的问题。带有限负载的一致性哈希和带虚拟节点的一致性哈希进一步解决了负载不均衡的问题。
代码地址:https://github.com/Justin02180218?tab=repositories
更多【分布式专辑】系列文章,请关注公众号

|