Memcached之内存利用率提升经验分享

fmms 12年前

在使用Memcached的时候,大部分人可能很少关注内存利用率,因为Cache毕竟是Cache,那么对于存储数据的可靠性要求就不高,丢了也不心疼,总之能够从DB中Reload回来即可,话是不错!但是在我们的项目中,却遇到了一个棘手的问题,就是希望数据能够被Memcached 100%的Cache起来,那么如何让容量规划做到位呢,加多大内存才是合理的,问题就随之而来了!在我们的测试过程中,想要放入20G的数据(5000 万)到开了25G的Memcached中,本想是没有问题的,结果才放到45%的容量的时候,数据就发生了“Evict”的现象,后续再继续追加数据,直到全部加完,Memcached的利用率始终维持在50%左右,丢了40%左右的数据,难道说需要开个50G的Memcached才能搞定20G的数据存储?这个也是不确定的,资源浪费太严重了,所以我们就开始着手分析原因了,目的就为两点:

① 100%将数据Cache起来

② 提高Memcached的内存利用率,节省不必要的成本,将利用率稳定在一定合理值,这样便于将来我们预警容量问题,也能做好容量规划

1、 首先:我们需要了解Memcached的内存分配机制:

Memcached采用了Slab Allocation机制,这种机制的好处是预先分配好内存,无需在使用时Malloc和Free,解决了内存碎片问题,分配好的内存也可以重复使用,所以可以减轻内存管理的负担。

分配过程:先初始化若干个Slab,为每个Slab分配一个Page的内存空间,Page缺省为1MB,每个Page根据Slab规格被划分为若干个相同 Size的Chunk,每个Chunk里保存一个Item,每个Item同时包含了Item结构体、k和V,相同Size的Chunk合成一组Slab Class,如图例:

我们可以使用Growth Factord对分配策略进行调优,默认值是1.25,Growth Factor的值不同,分配的Chunk的Size就不同,且不同Slab Class中的Chunk数量也会不同,根据需要调整才是。

n 缺省值F=1.25的情况下,Slab分配情况如下:

slab class 1: chunk size 88 perslab 11915

slab class 2: chunk size 112 perslab 9362

slab class 3: chunk size 144 perslab 7281

slab class 4: chunk size 184 perslab 5698

slab class 5: chunk size 232 perslab 4519

slab class 6: chunk size 296 perslab 3542

slab class 7: chunk size 376 perslab 2788

slab class 8: chunk size 472 perslab 2221

slab class 9: chunk size 592 perslab 1771

slab class 10: chunk size 744 perslab 1409

n 若F=2的情况下,Slab分配情况如下:

slab class 1: chunk size 128 perslab 8192

slab class 2: chunk size 256 perslab 4096

slab class 3: chunk size 512 perslab 2048

slab class 4: chunk size 1024 perslab 1024

slab class 5: chunk size 2048 perslab 512

slab class 6: chunk size 4096 perslab 256

slab class 7: chunk size 8192 perslab 128

slab class 8: chunk size 16384 perslab 64

slab class 9: chunk size 32768 perslab 32

slab class 10: chunk size 65536 perslab 16

2、 其次,了解数据存储的原理:

Memcached在收到需要存储的数据后,先计算数据的Size,最大存入1M的数据,然后选择适合的Slab Class中的Chunk(当然Memcached会记录哪些Chunk是空闲的),,如图例:

在数据存入选中的Slab Class,可能有以下两种情况:

n 若该Slab Class未满(free_chunks > 0),则成功存入;

n 若该Slab Class满了(free_chunks=0),有可能发生以下两种情况:

l 有可用内存,则新增一个Page的内存给该Slab,该Page也是被当前Slab的同样规格进行划分若干Chunk,然后将数据存入新增的Chunk中;

l 无可用内存,有可能发生以下两种情况:

u 启用LRU(LRU的Scope只是针对Slab的,而非全局),将新数据替换老数据,老数据丢失

u 禁用LRU(追加Memcached的启动参数-M),发生Out Of Memory错误

由于分配的都是定长Chunk,在内存利用率上也有天然的缺陷,如图例:

n 不仅仅存在单个Chunk上浪费现象

n 而且会存在整个Slab Class浪费的现象,因为有时遇到的数据Size根本落不到这个Slab Class上都是可能的

总之,Memcached可谓用心良苦,用空间换取性能,当然应用在使用时需要注意到这点特性,才能用好Memcached。

3、 最后,就必须要对自己应用的数据Size分布做个透彻的分析,才能将Memcached物尽其用:

根据经验所得:

n 数据Size的分布尽量集中可以提高Memcached的内存利用率

以我遇到的问题做为例: 我们需要在Memcached里保存User的操作记录,为了贪图查询的方便,我们就Key<UserID> = Value<List<操作记录>>,当每位User的操作记录数量从1到n都有分布,那么n较大的数据就会选择了Size较大的Chunk进行储存,若出现17193bytes大小的数据,那么只能从以下两个Slab中选择Slab Class 25进行存储:

slab class 24: chunk size 17192 perslab 60

slab class 25: chunk size 21496 perslab 48

如果这样Size的数据非常多的话,那就非常杯具了,浪费的内存成(21496-17193)*m线性增长;因此我们改变了存储策略,使用Key< 操作记录ID> = Value<操作记录>, Key<UserId> = Value<List<操作记录 ID>>进行存储,这样操作记录的每对K/V的Size都在200bytes左右,结果内存利用了提升到90%左右;

n 根据Value的Size分布,适当调整的Growth Factor也是可以提升内存利用率

n 最后,Memcached本身就是解决不可靠数据的存取服务的,做为二级缓存是不错的选择,可以很大程度的提升性能,但是要想存取可靠的数据,就不能选择Memcached,选择一项“适合的”技术保障可用性才是王道。