Memcached的分布式算法

JerryXia 发表于 , 阅读 (867)

Memcached虽然被称为分布式缓存服务,但服务器端并没有真正的“分布式”功能。服务器端仅包括内存存储功能,至于Memcached的分布式,则是完全由客户端实现的。

现在开始先简单地介绍一下其原理,各个客户端的实现基本相同。

下面假设Memcached服务器有node0、node1、node2三台,应用程序要保存键名为"Jerry0"、"Jerry1"、"Jerry2"、"Jerry3"、"Jerry4"的数据。

memcached-001

首先向Memcached中添加"Jerry0",将"Jerry0"传给客户端程序库后,客户端实现的算法就会根据“键”来决定保存数据的Memcached服务器,服务器选定后,即命令它保存"Jerry0"及其值。

memcached-002

同样,"Jerry1"、"Jerry2"、"Jerry3"、"Jerry4"都是先选择服务器再保存。

接下来获取保存的数据。获取时也要将要获取的键"Jerry0"传递给函数库。函数库通过与数据保存时相同的算法,根据“键”选择服务器。使用的算法相同,就能选中与保存时相同的服务器,然后发送Get命令。只要数据没有因为某些原因被删除,就能获得保存的值。

memcached-003

这样,将不同的键保存到不同的服务器上,就实现了Memcached的分布式。memcached服务器增多后,键就会分散,即使一台memcached服务器发生故障无法连接,也不会影响其他的缓存,系统依然能继续运行。

Memcached的分布式方法:根据余数计算分散

根据余数计算分散的方法简单来说,就是根据服务器台数的余数进行分散。求得键的整数哈希值,再除以服务器台数,根据其余数来选择服务器。

下面将使用伪代码来进行说明。


var nodes = { "node0", "node2", "node3" };
var keys = { "Jerry0", "Jerry1", "Jerry2", "Jerry3", "Jerry4" };

foreach(var key in keys)
{
    var code = Crc32(key);               // crc算值, 就是HashCode
    int mod = code % (nodes.Length + 1);
    var server = nodes[mod];             //根据余数选择服务器
    Console.WriteLine("{0} => {1}", key, server) 
}

首先求得字符串的CRC值,根据该值除以服务器节点数目得到的余数决定服务器。上面的代码执行后输入以下结果:

Jerry0 => node0
Jerry1 => node2
Jerry2 => node1
Jerry3 => node0
Jerry4 => node0

根据该结果,“Jerry0”分散到node0,“Jerry1”分散到node2等。

注意!当选择的服务器无法连接时,这时候会将连接次数添加到键上,再次计算哈希值并尝试连接,这个动作称为ReHash,这样就能够保证在Set/Get操作前获取到的机器是同一台。

根据余数计算分散的缺点

余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。那就是当添加或移除服务器时,缓存重组的代价相当巨大。添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服务器,从而影响缓存的命中率。

写段代码来验证其代价,代码功能:将“a”到“z”的键保存到Memcached。结果如下:

首先,当服务器只有三台时:

node0: a,c,d,e,h,j,n,u,w,x
node1: g,i,k,l,p,r,s,y
node2: b,f,m,o,q,t,v,z

结果如上,node0保存a、c、d、e……,node1保存g、i、k……, 每台服务器都保存了8个到10个数据。接下来增加一台Memcached服务器。

node0: d,f,m,o,t,v
node1: b,i,k,p,r,y
node2: e,g,l,n,u,w
node3: a,c,h,j,q,s,x,z

添加了node3。可见,只有d、i、k、p、r、y命中了。像这样,添加节点后键分散到的服务器会发生巨大变化。26个键中只有六个在访问原来的服务器,其他的全都移到了其他服务器。命中率降低到23%。在Web应用程序中使用memcached时,在添加memcached服务器的瞬间缓存效率会大幅度下降,负载会集中到数据库服务器上,有可能会发生无法提供正常服务的情况。

一致性哈希(Consistent Hashing)

一致性哈希如下所示:首先求出Memcached服务器(节点)的哈希值,并将其配置到0 ~ 2^32的圆上。然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过2^32仍然找不到服务器,就会保存到第一台memcached服务器上。

memcached-004

从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率,但一致性哈希中,只有在圆环上增加服务器的地点顺时针方向的第一台服务器上的键会受到影响。

memcached-005

因此,一致性哈希最大限度地抑制了键的重新分布。而且,有的一致性哈希的实现方法还采用了虚拟节点的思想。使用一般的hash函数的话,服务器的映射地点的分布非常不均匀。因此,使用虚拟节点的思想,为每个物理节点(服务器)在圆环上分配100~200个点。这样就能抑制分布不均匀,最大限度地减小服务器增减时的缓存重新分布。

通过上文中介绍的使用一致性哈希(Consistent Hashing)算法的Memcached客户端函数库进行测试的结果是,由服务器台数(n)和增加的服务器台数(m)计算增加服务器后的命中率计算公式如下:

(1 - n/(n+m)) * 100

总结

本次介绍了Memcached的分布式算法,主要有Memcached的分布式是由客户端库实现,以及高效率地分散数据的一致性哈希(Consistent Hashing)算法。

添加新评论