背景
当有多台缓存机器,当把对象存储到这些缓存机器上时,必须考虑映射算法,以便保证当缓存服务器的数量有增减的时候,尽量保证请求的数据能够在缓存中找到。
映射算法 一
假设有一个对象objA需要存储到两台缓存服务器中,那么我们可以为objA分配一个key,例如叫objA-key,然后使用下面算法
hash(objA-key)%cacheServerCount
这里的cacheServerCount是指缓存服务器的数量,在这里它等于2。如果是使用java的朋友,想要得到对象的hash值,可以如下调用:
objA-key.hashCode();
这样可以得到一个32位的整形数字,假设这里得到的hash值是1。至于缓存服务器,可以使用一个对象数组来表达:
Object [] cacheArray = new Object[cacheServerCount];
cacheServerCount
表示缓存服务器的数量。
这样通过下面的方式,就可以建立objA和缓存服务器的映射了。
cacheArray[1] = objA;
如果缓存服务器的数量一直没变化,那么上面的算法是可以稳定的工作的。但是一旦缓存服务器的数量增加了或者减少了,那么使用objA-key去缓存服务器中查找时,是找不到objA的。原因见下文。
假设增加了一台缓存服务器,映射算法变成:
hash(objA-key)%(cacheServerCount + 1);
这个时候得到的objA-key的hash值就不是之前的1了,假设是2,根据2去cacheArray中查找,肯定找不到objA这个对象的。这样就导致了大量的key无法找到对应的缓存数据。必须找到另一种算法来解决这个问题。
一致性hash算法
当缓存服务器增加或者减少时,如何尽量让之前的缓存数据按照之前的key仍然可以找到呢?也就是之前的key和value的映射关系尽可能不变。
环形hash值空间
hash算法算出来的值是一个32位的整形数字,它可以表示0~2^32-1的数字空间,我们可以把这个数字空间用圆形数值空间来表示。
如果把缓存服务器的ip作为key,那么算出的hash值也是落在这个圆形数值空间里面的。这其实就是一致性hash的基本思想,把要存储的对象和缓存服务器都映射到同一个数值空间。
在这里借用网络上的一些图片作为说明。
上图中,有四个对象Object1,Object2,Object3,Object4,给它们分配的key分别是key1,key2,key3,key4,需要存储到Cache A,Cache B和Cache C上,缓存服务器ip地址对应分别是Key A,Key B,Key C。
key1.hashCode();
key2.hashCode();
key3.hashCode();
key4.hashCode();
key A.hashCode();
key B.hashCode();
key C.hashCode();
这样的话缓存服务器ip和需要存储的对象就都映射在一个圆环数值空间里了。
现在的问题是,Object1,Object2,Object3,Object4 分别要存在哪个缓存服务器呢?
在这个环形空间中,如果沿着顺时针方向从对象的 key 值出发,直到遇见一个 cache ,那么就将该对象存储在这个 cache 上
那么根据上面的方法,对象 object1 将被存储到 cache A 上; object2 和 object3 对应到 cache C ; object4 对应到 cache B;
假设我们增加了一台缓存Cache D,并且它的key的hash值落在了key3和key2之间,那么受到影响的只有Object2。
本文引用参考的文章:
http://xiexiejiao.cn/java/memcached-consistent-hashing.html