您的位置:首页 > 数据库 > Memcache

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

2015-06-11 16:04 761 查看
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 python
import sys
from binascii import crc32
import hashlib

def 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 = tmp

if __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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: