memcached 之 哈希一致性 和 虚拟节点 分析

来源:互联网 发布:uwb定位算法 编辑:程序博客网 时间:2024/06/01 10:45

memcached 是一个简单高效的分布式内存缓存系统。

对于memcached service 来说,行为比较简单,存储key-value数据,并且memcached 相互之间并不通信,所以不能提供HA, load balance的功能。

这里的分布式实际是由客户端连接到不同的memcached,来做数据set/get 操作,这种数据分发行为完全是有客户端来做的, 客户端使用了哈希一致性和虚拟机节点来做这种数据分发。


考虑下面的场景

memcached  可以将一台或多台机器上的内存提供给多个application 使用。
如下图, 2个应用程序,3个memcached service, app1 和app2 可以使用3个service 提供的内存做cache,其操作api比较简单,get, set。

这里数据的distribution, 是由客户端app1 来做的,app 将数据以key 分发到不同的memcached。
客户端app并不记录某个数据记录在某个service 上(这样在数据量很大的时候,会出现性能的问题),
所以app 去get/set 某个数据的时候, app以key分配到不同的service上, 考虑最简答的模运算,如上场景有3个service(srvCount=3), 插入12个记录,分别hash(key) 分别为:
0,1,2,3,4,5,6,7,8,9,11,11
srvID =  hash(key) % srv0 那么这六个数据的分布就是
srv0: 0,3,6,9
srv1: 1,4,7,10
srv2: 2,5,8,11
当我们增加一个memcached service (srv3)后,同样算法,就出现如下分布:
srv0: 0,4,8,
srv1: 1,5,9
srv2: 2,6,10
srv3: 3,7,11
这样发现数据分布跟之前的差距非常大,
考虑测试场景
1. app连接3个service, 插入10k个数据
2. 减少一个service, 查询插入的10k个数据,查看命中的几率
3. 增加一个service, 查询插入的10k个数据,查看命中的几率

像我们那样简答的模运算,当出现客户端app使用的service 出现增删的时候,就会出现大量数据不能命中,导致大量cache 失效, 哈希一致性算法就能增加这种增删service 时的命中率。

这里使用  memcache 的两个python 客户端库 python-memcached 和  pylibmc   来进行我们设定的场景测试 
https://github.com/trumanz/pymemcached/blob/master/scale_test.py


python-memcached 只是简单取模运算, pylibmc 使用了 ketama 哈希一致性算法:结果如下


 pylibmcpython-memcached删除memcached节点65.37000033.470000增加memcached  节点 73.70000025.620000


可以看出 ketama 哈希一直算法,在删除节点后,理论剩余66.67%的数据,其仍然能命中绝大部分剩余的数据,增加节点也能命中较多的数据,而python-memcached则有较大的cache失效。



1.  ketama 哈希一致性算法 
http://cn.last.fm/user/RJ/journal/2007/04/10/rz_libketama_-_a_consistent_hashing_algo_for_memcache_clients
哈希一致性算法,需要将server 地址做hash 运算,如下如图圆环A所示, 每个service 在圆环中占据不同的位置,数据做hash运算后在这个圆环上分配,分配到某个service的范围内是将数据分发到这个service上,(这样就在增删节点的时候server在圆圈上的位置不变从而增加了命中率)。

考虑增加节点的情况A-->B ,新加入的srv4 占据了一定范围的圆环,黄色部分原本分配到srv0的数据,现在指向了srv4, 这部分cache 就出现失效。



2. 虚拟节点
如上图,圆环A,B存在一个问题,就是数据不能均匀的分配到service上,并且当多个service 的内存大小不同时,也不能按照权重分配数据,这里引入了虚拟节点来解决这个问题,其实非常简单,就是将每个servie的地址由一个转变成更多的地址,
srv1转变成srv1-0 和srv1-1 两种地址(也就是两个虚拟节点), 这样srv1 就能得到两个hash值,然后在圆环中占据两个位置,这样就变成了圆环C的情况,这样针对每个service 增加更多的虚拟节点就能实现按照权重的分配数据,如srv0有100M内存,srv1 有50M内存,那么
srv0可以由srv0-0,srv0-1,  ..., srv0-99,  100个虚拟节点组成
srv1可以由srv1-0,srv1-1,  ..., srv1-50,   50个虚拟节点组成

这样这些虚拟节点在圆环上的随机分配,一般就能保证数据按照权重分配到不同的service上。

测试代码如下,结果基本与pylibmc 一致
https://github.com/trumanz/pymemcached/blob/master/myketama.py

#!/usr/bin/env pythonimport sysfrom binascii import crc32import hashlibdef myhash(key):    d =   hashlib.md5(key).hexdigest()    return  [int(d[0:8], 16), int(d[8:16], 16), int(d[16:24], 16), int(d[24:32], 16)]    return  ((crc32(key) & 0xffffffff  >> 16) & 0x7fff) or 1    class SRV:    def __init__(self, addr):         self.addr = addr         self.keys = set()         self.real_weight = 0    def __str__(self):         return  self.addr  +  ", has "  + str(len(self.keys))    def __repr__(self):         return str(self)srvs = []srvs_points = []def getSrv(key):   l = 0   r = len(srvs_points) -1;   v = myhash(key)[0]   if r == 0:       return k[0]   if v >= srvs_points[r][0] or v < srvs_points[0][0]:      return srvs_points[r][1]   while (r - l) > 1:      m = (l + r)/2      if v  < srvs_points[m][0]:        r = m      else:        l = m   return srvs_points[l][1]       def addSrv(addr):  s = SRV(addr)  srvs.append(s)   for i in range(0,40):     haddr = addr + '-' + str(i)      hcode = myhash(haddr)     for h  in hcode:        srvs_points.append((h, s))  srvs_points.sort(lambda p1, p2 : cmp(p1[0], p2[0]))def delSrv(addr):   global srvs_points   tmp = [x for x in  srvs_points if x[1].addr != addr  ]   srvs_points = tmpif __name__ == '__main__':   srvaddrs = ['SRV_A', 'SRV_B', 'SRV_C']   for addr in srvaddrs:      addSrv(addr)   for key in range(0, 10*1000):        s = getSrv(str(key))        s.keys.add(key)   print "After set 10K  in 3 server"   print "len of  srvs_points=%d"%(len(srvs_points))   print srvs   print "Test Add one Server"   addSrv('SRV_D')   print "len of  srvs_points=%d"%(len(srvs_points))   hit = 0   for key in range(0, 10*1000):       s  = getSrv(str(key))       if key in s.keys:          hit = hit+1   print " hit=%d"%(hit)   print "Test del one Server"   delSrv('SRV_D')   delSrv('SRV_C')   print "len of  srvs_points=%d"%(len(srvs_points))   hit = 0   for key in range(0, 10*1000):       s  = getSrv(str(key))       if key in s.keys:          hit = hit+1   print " hit=%d"%(hit)





root@test:~/pymemcached# ./myketama.py 
After set 10K  in 3 server
len of  srvs_points=480
[SRV_A, has 3184, SRV_B, has 3300, SRV_C, has 3516]
Test Add one Server
len of  srvs_points=640
hit=7245
Test del one Server
len of  srvs_points=320
hit=6484



0 0
原创粉丝点击