Memcached内存管理的局限性导致尽量不能让KEY永远不过期

Memcached内存管理的局限性导致尽量不能让KEY永远不过期

在分布式环境下,KEY永远不过期会导致潜在的“脏数据”的风险。本文从Memcached内存管理策略Slab Allocator的角度分析,KEY永远不过期的潜在风险。实验看,Slab Allocator也是存在缺陷的,这种缺陷的存在导致:(1)不同应用的缓存,尽量不要存储在同一个Memcached实例中;(2)KEY不要永远不过期。

内存管理策略:Slab Allocator

Slab Allocator简介:

当Memcached接收到一个KEY-VALUE时,Memcached向操作系统申请一个Page,每个Page是1M。Memcached先看下96B(启动时-n参数指定,默认是96B)是否能够容纳VALUE,如果不能,则放大f=1.25倍,看是否能容纳,不能再放大,直到容纳或者超过1M(1个Page大小)。现在我们假设放大了21次后,找到能容纳它的,于是创建一个编号为22的SLAB(编号1的SLAB不用乘以f),这个SLAB用来做什么呢?先把申请来的1Page按照chunk_size=96B*1.25^21切分,每个块叫chunk,每个chunk用来存储一个合适大小的KEY-VALUE。

如果后继的KEY-VALUE,容量大小有合适的SLAB,则在这个SLAB中存放,如果chunks不够,可以申请新的Page,再切分成chunks;如果没有容量合适的SLAB,则新建一个新的SLAB,道理类似。

存储结构如图所示:


实验:LRU驱逐是针对“合身的”SLAB的

Memcached分配256M内存

启动两个Memcached,各分配256M内存,用Replica做双向复制(其实实验只需要1个Memcached进程,只不过我的实验环境习惯了双向复制)。

 

在10.10.83.179:11203

/usr/local/bin/memcached -d -p 11203 -m 256-x 10.10.83.177 -X 11234 -u root -l 10.10.83.179 -c 256 -P/tmp/memcloud_11203_10.10.83.177_11234.pid

 

在10.10.83.177:11204

/usr/local/bin/memcached -d -p 11204 -m 256-x 10.10.83.179 -X 11234 -u root -l 10.10.83.177 -c 256 -P/tmp/memcloud_11204_10.10.83.179_11234.pid

 

另外,Memcached版本是1.4.5 ; Slab的起始chunk_size是默认的96B;增长率f是默认的1.25

观察1:执行SET命令前,查看slab状态

Memcached进程刚启动,没有任何slab被分配。


观察2:客户端执行SET

单线程,执行24157次SET指令,Value的大小是10K。数据的过期时间:2天。

客户端使用的是:XMemcached  (xmemcached-1.3.6.jar) 注意:这个客户端对Value超过20K的数据序列化时,似乎有问题,wireshark抓包看数据少发了。(勘误1:后来无意间发现容量小的Value与大的Value,SET命令的Flag是不一样的,其实人家XMemcached是压缩了。我孤陋寡闻了。)

代码如下:

 

执行过程中,不断查看stats slabs的状态:


当客户端停止的时候,我们存入了这些KEY:

K:T1K0; r:true; C:0 ms;

……

K:T1K24156; r:true; C:0 ms;

总共存入了:24157 个KEY。

上图的统计信息也显示:cmd_set命令截止到24157

STAT22:total_pages 257  表示内存已经用到了257M,因为1Page1M

STAT22:total_chunks 24158   STAT22:used_chunks 24157  只剩余了1chunk了。

STATtotal_malloced 268250432=(268250432/1024)/1024=255.8M

 

总的统计信息:stats显示截止目前没有“驱逐”任何的Item


观察3:同样大小的Value,再放入3个

把代码中的:numTime=2;numSetCmd=3

 

K:T2K0; r:true; C:125 ms;

K:T2K1; r:true; C:16 ms;

K:T2K2; r:true; C:0 ms;

日志显示3条都存入了。(时间代价相差那么大主要是客户端对连续多个SET有合并的功能)

 

 

命令cmd_set增加了3个,从24157增加到了24160;最后的一个空闲的chunk被占用了,按道理还缺少2个chunk的。查看stats的驱逐的确有两个被驱逐了“evictions 2”,Memcached已经开始启动LRU进行驱逐了。


此时内存使用率(填充率):STAT bytes 249299446 / STATlimit_maxbytes 268435456=92.8%

 

观察4:LRU驱逐,查看被驱逐的KEY


输出:符合LRU的驱逐算法

被驱逐的:T1K0

被驱逐的:T1K1

 

因为LRU的驱逐算法的存在,所以Memcached能够复用chunk的。

观察5:chunk复用的局限性

Memcached从操作系统以Page为单位“批发”内存后,把每个Page按照chunk_size切分成若干chunk,再以chunk为单位进行“零售”。Memcached永远不释放Page,而是不断复用chunk,观察4中的LRU驱逐让我们直观感受到了chunk复用。

 

问题:如果我们再向Memcached提交Value,而且Value的大小是现有chunk无法容纳的?这个时候,Memcached是拒绝呢?因为复用chunk是无法满足需要的,现有的chunk都太小了,不足以容纳更大容量的Value

我们把Value的chunkSize调整到12K;numTime=3;numSetCmd=1。提交1次12K的Value。

 

我们查看:stats slabs和stats的驱逐情况


 

统计数据显示:memcached新创建了一个slab,chunk_size是13880/1024=13.5K,批发了1个Page,可零售为:75个chunk,目前被用掉了1个。还剩余74个。

记住我们刚才的KEY是:K:T3K0; r:true; C:141 ms;

 

小结:Memcached不驱逐,也不拒绝。

1、  memcached必须Value的大小分配适合的chunk;

2、 Memcached启动时的-m参数,只是个近似的。在观察3中“STAT bytes 249299446/ STAT limit_maxbytes 268435456=92.8%”利用率到了92.8%的时候(还没到100%呀),就开始拒绝分配内存了。原因很简单:因为还要预留些给其他的chunk_sizeslab。否则,当我们填充更大的Value时,就没有Page了,没有Page必然没有chunk我们可以推断:MemcachedLRU驱逐只是局部于Slab范围的。

 

观察6:LRU驱逐局部于SLAB

继续以12K的填充75次,因为Memcached只剩余了74个可容纳它的chunks。


结果:SLAB23号的,cmd_set增加了75次,从1到76次了。而used_chunks从1到75,只增加了74个。说明又存在驱逐了:的确stats的驱逐从2变3了。



K:T4K0; r:true; C:0 ms;

K:T4K74; r:true; C:0 ms;

 

第三次的驱逐是驱逐谁了呢?如果LRU是全局的,那应该驱逐掉SLAB22里面的,KEY应该是“T1K2”;但是,驱逐SLAB22的,腾出的chunk是不够容纳12K的Value的,因此LRU应该是局部于SLAB的,应该驱逐SLAB23里面的,这样KEY应该是:“T3K0”


输出:

提取 K: T1K2; V:10240

驱逐:T3K0

实验数据显示:LRU驱逐不是全局的LRU,而是针对KEY-VALUE“合身的”SLABLRU

 

实验结论的意义:KEY不应该永远不过期

有了上面实验的结论:“LRU驱逐不是全局的LRU,而是针对KEY-VALUE“合身的”SLAB的LRU”。

 

如果使用Memcached的时候,KEY设置用不过期,会带来什么危害?

 

反证一下,如果KEY有个过期时间,那么即使在给定的时间面对大量的set,也会因为失效的KEY而腾出chunk,这样“热点SLAB”不至于把内存吃到92.8%。解释下:“热点SLAB”就是拥有Page最大的SLAB,同时拥有交多的读写操作(cmd_set)。因为一个应用的缓存的VALUE,多半会比较集中在某个chunk_size范围内,因此SLAB的分布往往是不平衡的。内存吃到92.8%,不是100%,就是上面实验显示的Memcached对热点SLAB只允许吃到92.8%,因为要预留内存给其他chunk_size的SLAB分配Page。当然这个92.8%应该跟memcached启动时默认-n=96B,-f=1.25是有关系的。

 

相反,如果KEY永远不过期,无疑会加剧,甚至非常快(比如应用跑上一周时间)“热点SLAB”(可能是1个,也可能是多个)把内存吃到92.8%。

 

在内存填充率到达92.8%的时候,如果由于应用数据需求的变化,导致新的热点SLAB的出现,那就糟糕了!!!因为当内存吃到92.8%的时,对于新的热点SLAB只能划出1个Page,也就是1M,这个新热点只能在1M的空间内LRU,无疑会大大降低命中率,缓存就变得没有意义了!!!

 

简单说,SLAB Allocator缺陷是:当内存填充率到达92.8%的时,应用不能出现新的“热点SLAB”。当然,92.8%只是极端情况,其实内存填充率到了60%,70%,80%的时候,新的“热点SLAB”也只能在剩余的32.8%,22.8%,12.8%的范围内LRU。因此,不同应用的缓存数据,尽可能不要存放在同一个memcached进程内,因为它们的热点SLAB的chunk_size往往不同,导致可共享的内存变小。KEY的永远不过期,会加剧热点SLAB分布太散

 

诊断我的Memcached是否生病了?

那么,我们如何诊断我们的Memcached是否生了上面说的病呢?综合三个方面:

1、  内存使用率(填充率):stats.bytes/ stats.limit_maxbytes

2、  命中率:stat.get_hits/ stat.cmd_get

3、  Active SLAB的个数,以及热点SLAB的数量:stats slabs.active_slabs 和 热点SLAB:最好同时具有多个Page和多个cmd_set。

 

以刚才的实验数据,简单说明下:


SLAB 22号,占据了92.8%的内存,消耗了257个Page,cmd_set24160。SLAB 23号,占据了1个Page,不过好在它的cmd_set才76个,也就是说SLAB 23并不是热点SLAB。读写的人点集中在SLAB 22,而SLAB 22充分享用了被分配的大部分内存,因此这个情况非常理想。但是如果SLAB 23的cmd_set 也等于24160,那就生病了,因为这样必然导致SLAB 23的命中率很低。

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值