关于key的有效期
key的有效期有3种格式:
- 秒数。从设定的数开始第N秒后失效。
- 时间戳。特定时间失效,例如在团购网站中,活动到中午12点失效,
add key 0 137920999 6s
。 - 0.不自动失效。有种误解:设置为0永久有效是错误的。因为编译memcached时可以指定一个常量,默认是30days,所以即使设置为0,30天后也会自动失效,也有可能等不到30天就会被新数据挤出去。
incr
和decr
命令的操作对象是unit32,范围是[0,2^31-1]。
考虑这样一种情形:申请20M内存,释放15M内存,然后又需要申请12M内存,这样就产生了3M的内存碎片,随着程序的运行,碎片会越来越多。memcached采用了Slab Allocator分配机制:基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块(chunk),并将尺寸相同的chunk分成组(chunk的集合),尽可能(完全解决内存碎片是不可能的)解决内存碎片问题。
根据收到的数据大小自动选择最合适的slab(具体实现是memcached中保存着slab空闲chunk的列表,根据这个列表选择空的chunk并将数据缓存其中),例如100bytes的item将会被存放在上图中的Slab2,但是剩下的12字节同样被浪费了,无法重新利用,这种做法只是尽可能减少内存碎片。使用memcached -vvv
启动服务的时候可以看到类似以下的输出:
slab class 1: chunk size 96 perslab 10922
slab class 2: chunk size 120 perslab 8738
slab class 3: chunk size 152 perslab 6898
slab class 4: chunk size 192 perslab 5461
slab class 5: chunk size 240 perslab 4369
slab class 6: chunk size 304 perslab 3449
slab class 7: chunk size 384 perslab 2730
slab class 8: chunk size 480 perslab 2184
仔细观察可以发现:120/96 = 152 / 120 = 1.25,这个chunk的增长比例就叫做增长因子(grow factor,默认1.25,启动时可使用-f命令行参数) 。
过期和删除机制警告:如果我们要存100bytes数据,正常情况下应该保存在chunk size为120bytes的仓库(即slab2),但是slab2已经满了,并不会选择存到更大的slab,例如slab3,而是将老的数据挤出去,其策略是惰性过期机制和LRU删除机制!。
- 当某个值过期时,并没有从内存中删除,因此stats命令统计的时候
curr_items
有其信息; - 取值的时候先判断是否过期,如果过期则返回空清空该key占用的chunk,
curr_items
就减少了; - 如果之前没有get过,将不会自动删除,当某个新值占用其位置时,将会被当做空chunk来用。
过期指的是让用户看不到数据,并不是在过期瞬间正真从内存中移除,被称为惰性失效(lazy expiration)。好处是节省了CPU时间和检测是否过期的成本。
删除机制使用的LRU,对key进行更新,查询将会刷新key为最新(被认为是新数据)。即使某个key被设置为永久有效期也一样会被踢出,这个就是大名鼎鼎的永久数据被踢的现象。以122bytes大小的chunk为例,如果122bytes的chunk满了,又有新的值需要加入(长度为120bytes),应该挤掉最近最久未使用的。
memcached的LRU的原理是:维护每个key的计数器,当某个key被请求时该key对应的计数器清零,其他key对应的计数器加1,最后比较哪个key的计数器最大,这个key就是LRU。
memcached中的一些参数限制- key的长度限制为250bytes,二进制协议支持65535字节。
- value限制为1M(100W字符)。
所谓的分布式就是将不同的键存储到多态服务器,这样就引来一个问题?如何确定键和服务器的对应关系?最容易的算法是将key转化成数字(例如hash签名算法或者CRC32),然后将这个数字对服务器数量进行MOD运算。这种算法存在一个很大的弊端:原先有3个server分别按照hash取余,存储的数据对应关系为:
server1 - key0,key3 # mod = 0
server2 - key1,key4 # mod = 1
server3 - key2 # mod = 2
如果server3宕机,查询key3的时候,计算出来的是3 mod 2 = 1
,即:我们应该到server2去取key3,肯定是无法找到的!然而key3对应的数据仍然好好滴在server1上存储着,一个严重的问题是:缓存命中率大大降低。
从数学上讲,当服务器从N变成N-1之后,在[0,NN-1]范围内只有[0,N-1]算出来的余数没有改变(一共N个数),即:每NN-1个key只有N个key的模没有变,命中率为:
N/(N*N-1) = 1/N-1
。所以10台服务器变成9台服务器的时候11台变成10台服务器的时候命中率只有10%了,下降了90%!,并且服务器越多后果越严重,买的服务器越多结果性能越差,这不就亏大了?
求余算法的另一个缺点是打乱了原有的数据存储,例如:原来有3台服务器,存储数据为
server1 - 0,3,6,9
server2 - 1,4,7
server3 - 2,5,8
当server2宕机时,按照求余算法,数据分布为:
server1 - 0,2,4,6,8
server3 - 1,3,5,7,9
可以发现原来的3和9在server1上好好滴放着被迁移到了server3,原来的2和8在server3上放着被迁移到了server1,我们理想的情况是将宕机的server2中的数据分配到server1和server3就行了。数据迁移是很耗性能的。
鉴于分布式取余的以上缺点,我们使用一致性哈希。
一致性哈希把服务器各个节点映射放在钟表的各个时刻上,吧key也映射到钟表的某个时刻上。该key沿着钟表顺时针走,碰到的第一个节点即为该key的存储节点。如图所示:
以上有2点疑问:
- 时钟上的指针的最大才11点,如果有上百个memcached节点该怎么办?始终只是为了便于理解做的比喻。在实际应用中我们可以在圆环上分布[0,2^32-1]个数字,这样全世界的服务器都可以装下了。
- 如何将“节点名”和“键名”转化为整数。可以使用现成的函数,例如:crc32()。也可以自定义转化规则,但是要注意转化的碰撞率要低(碰撞值的是:不同的输入得到了相同的输出)。
从上图可以看出:当某个节点宕机之后哦只影响该节点顺时针之后的一个节点,而其他节点不受此影响,一致性哈希最大限度抑制了键的重新分布。
一致性哈希+虚拟节点对缓存命中率的影响由上图我们可以看出,在理想状态下:
- 节点在圆环上分配均匀,因此承担的任务也是平均的,但事实上:一般的哈希函数对于节点在圆环上的映射并不均匀。
- 当某个节点宕机之后直接冲击下一个节点,对下一个节点的冲击过大,所以能否把down掉的节点上的压力平均分配到其他所有存活着的节点上?
我们可以通过引入虚拟节点完全解决上述问题。
每个真实节点可以映射成M个虚拟节点,则N个真实节点就映射为MN个虚拟节点散列在圆环上。各个真实节点对应的虚拟节点交错分布*。这样,某个真实节点down掉之后,则把其影响分到到其他所有节点上。如图所示:
即:N台服务器变成N-1台服务器之后,剩下的N-1台服务器分担宕机的1台服务器的影响,从而不命中率为1/N-1,和分布式取模算法刚好相反!java代码实现,这个实验模拟了当5台服务器挂掉一台之后不同算法对缓存命中率的影响:
共同学习,写下你的评论
评论加载中...
作者其他优质文章