普通的哈希算法采用简单取模的方式,将缓存服务器进行散列,通常情况下是没有问题的,但是当缓存服务器的个数发生变动时,将会产生较大的影响

如上图所示,之前有4台缓存服务器,当增加1台缓存服务器之后,除数的变化(4 -> 5)导致求模结果变化,所有缓存查询均未命中
即缓存服务器的个数发生变化时,在一段时间内(缓存重建完毕之前),会有大量缓存查询未命中,导致这段时间内的服务整体性能下降特别严重
一致性哈希算法能有效降低服务器个数变化对整体缓存的影响,基本实现原理是将Hash函数的值域空间组织成一个圆环,将服务器节点进行哈希,并将哈希结果映射到圆环上,当有一个写入缓存的请求到来时,使用相同的Hash函数,计算Key的哈希值在圆环上对应的位置,按顺时针方向,将请求定位至离其最近的服务器节点

如下图所见,当增加一台缓存服务器Server5后,Server4和Server5之间的点将被定位至Server5,Server5和Server之间的点依然定位至Server,并且对Server2,Server3和Server4没影响,比起简单的求模哈希,未命中的缓存查询少了很多,整体服务性能不会下降过大

当然在实际使用过程中会在圆环上添加很多虚拟缓存服务器节点,以便缓存分布更加均匀
介绍完原理,我们再来看一下具体实现,以Memcached-java-client为例
如果我们想使用一致性哈希算法,只需要添加pool.setHashingAlg(SockIOPool.CONSISTENT_HASH);这行代码即可
import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;
public class Test {
public static void main(String[] args) {
MemCachedClient client = new MemCachedClient();
String[] servers = {"192.168.52.129:9999",
"192.168.52.131:9999"};
Integer[] weights = {1, 1};
SockIOPool pool = SockIOPool.getInstance();
pool.setServers(servers);
pool.setWeights(weights);
pool.setInitConn(5);
pool.setMinConn(5);
pool.setMaxConn(250);
pool.setMaxIdle(1000 * 60 * 60 * 6);
pool.setMaintSleep(30);
pool.setNagle(false);
pool.setSocketTO(3000);
pool.setSocketConnectTO(0);
pool.setHashingAlg(SockIOPool.CONSISTENT_HASH);
pool.initialize();
client.set("test", "This is a test String");
String test = (String) client.get("test");
System.out.println(test);
}
}
来看下实际效果
sean@ubuntu1:~$ telnet 192.168.52.131 9999
Trying 192.168.52.131...
Connected to 192.168.52.131.
Escape character is '^]'.
get test
END
sean1@ubuntu2:~$ telnet 192.168.52.129 9999
Trying 192.168.52.129...
Connected to 192.168.52.129.
Escape character is '^]'.
get test
VALUE test 32 21
This is a test String
END
先从SockIOPool的初始化开始
public void initialize() {
......
if (this.hashingAlg == 3)
populateConsistentBuckets();
else
populateBuckets();
......
}
构建一致性哈希算法中的整个圆环,当然从具体实现上来看只是构建虚拟节点的集合
private void populateConsistentBuckets(){
this.consistentBuckets = new TreeMap();
MessageDigest localMessageDigest = (MessageDigest)MD5.get();
// 获得总权重
// 如果指定了每个服务器的权重,则其和值为总权重
// 否则每个服务器权重为1,总权重为服务器个数
if ((this.totalWeight.intValue() <= 0) && (this.weights != null))
for (i = 0; i < this.weights.length; ++i){
SchoonerSockIOPool localSchoonerSockIOPool = this;
(localSchoonerSockIOPool.totalWeight = Integer.valueOf(localSchoonerSockIOPool.totalWeight.intValue()
+ ((this.weights[i] == null) ? 1 : this.weights[i].intValue())));
}
else if (this.weights == null)
this.totalWeight = Integer.valueOf(this.servers.length);
// 循环遍历每一个服务器以便创建其虚拟节点
for (int i = 0; i < this.servers.length; ++i){
int j = 1;
if ((this.weights != null) && (this.weights[i] != null))
j = this.weights[i].intValue();
// 每个服务器的虚拟节点个数需参照该服务器的权重
double d = Math.floor(40 * this.servers.length * j / this.totalWeight.intValue());
long l = 0L;
// 循环构建每一个节点
while (l < d){
byte[] arrayOfByte = localMessageDigest.digest(this.servers[i] + "-" + l.getBytes());
for (int k = 0; k < 4; ++k){
Long localLong = Long.valueOf((arrayOfByte[(3 + k * 4)] & 0xFF) << 24
| (arrayOfByte[(2 + k * 4)] & 0xFF) << 16
| (arrayOfByte[(1 + k * 4)] & 0xFF) << 8
| arrayOfByte[(0 + k * 4)] & 0xFF);
// 将每个虚拟节点添加到圆环中
this.consistentBuckets.put(localLong, this.servers[i]);
}
l += 1L;
}
Object localObject;
// 构建socket工厂类
if (this.authInfo != null)
localObject = new AuthSchoonerSockIOFactory(this.servers[i], this.isTcp, this.bufferSize,
this.socketTO, this.socketConnectTO, this.nagle, this.authInfo);
else
localObject = new SchoonerSockIOFactory(this.servers[i], this.isTcp, this.bufferSize,
this.socketTO, this.socketConnectTO, this.nagle);
// 使用socket工厂类创建连接池
GenericObjectPool localGenericObjectPool = new GenericObjectPool((PoolableObjectFactory)localObject,
this.maxConn, 1, this.maxIdle, this.maxConn);
((SchoonerSockIOFactory)localObject).setSockets(localGenericObjectPool);
// 每个服务器都有自己的连接池
this.socketPool.put(this.servers[i], localGenericObjectPool);
}
}
MemcachedClient的初始化方法,通过该方法可确定Client的具体实现类为AscIIUDPClient
public MemCachedClient() {
this(null, true, false);
}
public MemCachedClient(String paramString, boolean paramBoolean1,
boolean paramBoolean2) {
this.BLAND_DATA_SIZE = " ".getBytes();
if (paramBoolean2)
this.client = new BinaryClient(paramString);
else
this.client = new AscIIUDPClient(paramString);
}
当发送一个添加请求时,本质还是通过调用set方法实现的
public boolean add(String paramString, Object paramObject) {
return set("add", paramString, paramObject, null, null,
Long.valueOf(0L));
}
// paramInteger的值为null
private boolean set(String paramString1, String paramString2,
Object paramObject, Date paramDate, Integer paramInteger,
Long paramLong) {
......
SchoonerSockIO localSchoonerSockIO = this.pool.getSock(paramString2,
paramInteger);
......
}
服务器的查找过程如下
public final SchoonerSockIO getSock(String paramString, Integer paramInteger) {
......
// 计算Key的哈希值,并根据该哈希值得到对应的服务器节点哈希值
long l = getBucket(paramString, paramInteger);
// 根据服务器节点哈希值得到对应的服务器
String str1 = (this.hashingAlg == 3) ? (String) this.consistentBuckets
.get(Long.valueOf(l)) : (String) this.buckets.get((int) l);
while (!(((Set) localObject).isEmpty())) {
// 从服务器连接池中获取到特定服务器的连接
SchoonerSockIO localSchoonerSockIO = getConnection(str1);
......
}
首选根据Key值计算出其哈希值(getHash),然后根据得到的哈希值确定其在圆环上对应的服务器节点(findPointFor)
// paramInteger的值为null
private final long getBucket(String paramString, Integer paramInteger) {
long l1 = getHash(paramString, paramInteger);
if (this.hashingAlg == 3)
return findPointFor(Long.valueOf(l1)).longValue();
long l2 = l1 % this.buckets.size();
if (l2 < 0L)
l2 *= -1L;
return l2;
}
Key的哈希值计算过程如下,和populateConsistentBuckets方法中用来生成服务器虚拟节点哈希值的算法是一样的
// paramInteger的值为null
private final long getHash(String paramString, Integer paramInteger) {
if (paramInteger != null) {
if (this.hashingAlg == 3)
return (paramInteger.longValue() & 0xFFFFFFFF);
return paramInteger.longValue();
}
switch (this.hashingAlg) {
case 0:
return paramString.hashCode();
case 1:
return origCompatHashingAlg(paramString);
case 2:
return newCompatHashingAlg(paramString);
case 3:
return md5HashingAlg(paramString);
}
this.hashingAlg = 0;
return paramString.hashCode();
}
private static long md5HashingAlg(String paramString) {
MessageDigest localMessageDigest = (MessageDigest) MD5.get();
localMessageDigest.reset();
localMessageDigest.update(paramString.getBytes());
byte[] arrayOfByte = localMessageDigest.digest();
long l = (arrayOfByte[3] & 0xFF) << 24 | (arrayOfByte[2] & 0xFF) << 16
| (arrayOfByte[1] & 0xFF) << 8 | arrayOfByte[0] & 0xFF;
return l;
}
在圆环上查找Key的哈希值对应的服务器节点哈希值
参照populateConsistentBuckets中的代码,所有虚拟节点被存放在一个TreeMap中,所以这里可以使用tailMap方法获得大于等于Key哈希值的子树,然后获取该树中最小值即可
private final Long findPointFor(Long paramLong) {
SortedMap localSortedMap = this.consistentBuckets.tailMap(paramLong);
return ((localSortedMap.isEmpty()) ? (Long) this.consistentBuckets
.firstKey() : (Long) localSortedMap.firstKey());
}
分享到:
相关推荐
一致性哈希算法最初由麻省理工学院的K等人提出,并被广泛应用于分布式系统中,以解决节点动态变化时数据一致性问题。其核心思想是通过引入哈希环,将数据对象均匀分布在哈希环上的不同节点中,以此降低节点变更对...
一致性哈希算法是一种在分布式系统中解决数据分片和负载均衡问题的算法,它主要解决了在动态添加或移除节点时,尽可能少地改变已经存在的数据分布。在云计算和大数据处理领域,一致性哈希被广泛应用,例如在分布式...
一致性哈希算法通过将哈希值空间组织成一个虚拟的环状结构,使得每个存储节点仅负责环上的一段区域,从而有效减少了节点变化时的数据迁移量。然而,一致性哈希算法也存在一些问题,比如在节点数量较少时,节点间的...
一致性哈希算法是一种分布式哈希(Distributed Hash Table, DHT)技术,它解决了在分布式环境中数据分片和负载均衡的问题。在传统的哈希算法中,如果增加或减少服务器节点,会导致大量数据重新分配,而一致性哈希...
本文针对这一问题,深入研究了一致性哈希算法在分布式数据库扩展中的应用,并提出了一种创新的扩展方法,旨在提高扩展效率,降低扩展成本,为大数据环境下的数据库管理带来新的优化方案。 一致性哈希算法最初由...
一致性哈希算法是一种分布式哈希表(DHT)中用于解决数据分片和负载均衡问题的算法。在大型分布式系统中,例如缓存系统、分布式数据库等,一致性哈希能够确保当节点加入或离开时,尽可能少的数据需要迁移,从而保持...
一致性哈希算法是一种分布式哈希(Distributed Hash Table, DHT)技术,旨在解决在分布式环境中数据分布不均匀的问题。Ketama算法是基于一致性哈希的一种优化实现,由Last.fm公司的Simon Willison提出,其目标是在...
一致性哈希算法是一种在分布式系统中用于解决数据分发和负载均衡问题的算法。随着互联网技术的快速发展,分布式系统已经成为支撑大规模服务的关键技术之一。在分布式系统中,多个节点通过网络协同工作,提供高可用性...
### 一致性哈希算法及其在分布式系统中的应用 #### 摘要 一致性哈希算法是一种用于解决分布式系统中节点动态变化导致的数据重新分布问题的关键技术。它通过将哈希空间映射到一个循环的空间中,实现了数据节点的高效...
一致性哈希算法应用及优化是IT领域中分布式系统设计的核心技术之一,特别是在处理大规模数据分布与缓存系统中,其重要性不言而喻。本文将深入探讨一致性哈希算法的基本概念、工作原理以及在实际场景中的应用和优化...
一致性哈希算法作为解决这一问题的重要手段之一,近些年来得到了广泛关注和应用。 一致性哈希算法由David Karger等人在1997年提出,它是一种特殊的哈希算法,主要用于分布式系统中实现负载均衡。与传统的哈希算法...
一致性哈希算法
白话解析:一致性哈希算法1 一致性哈希算法是解决分布式缓存问题的解决方案。缓存服务器数量的变化会引起缓存的雪崩,导致整体系统压力过大而崩溃。为了解决这个问题,一致性哈希算法诞生了。 在了解一致性哈希...
一致性哈希算法(Consistent Hashing)是一种在分布式系统中实现负载均衡的算法,尤其在分布式缓存如Memcached和Redis等场景下广泛使用。它解决了传统哈希算法在节点增减时导致的大量数据迁移问题,提高了系统的可用...
本项目以“基于NIO-EPOOL模型netty实现的具备一致性哈希算法的NAT端口映射器”为主题,深入探讨了Netty在NAT端口映射中的应用,以及一致性哈希算法在此过程中的作用。 首先,我们来了解NIO(Non-blocking I/O,非...
#资源达人分享计划#
#资源达人分享计划#
本文将探讨一个名为"ufire-springcloud-platform"的项目,该项目是基于一致性哈希算法实现WebSocket分布式扩展的一次尝试,并结合Jenkins、GitHub Hook和Docker Compose实现了自动化持续部署。 1. **一致性哈希算法...