注册 登录
  • 欢迎访问"运维那点事",推荐使用Google浏览器访问,可以扫码关注本站的"微信公众号"。
  • 如果您觉得本站对你有帮助,那么可以扫码捐助以帮助本站更好地发展。

Memcached内存分配策略

Memcached 彭东稳 8239次浏览 已收录 0个评论

在说Memcached内存分配之前,先说一下Linux内存管理中用到的两个避免内存碎片的机制。

Buddy system:伙伴系统避免内存外碎片。Linux内核分配内存的方式是按照页框(默认4k大小)进行的。由于页框大小是4k,而很信息打开时需要用到几个页框,外碎片就是指当内核想找一个大页框用来存储某些信息时都无法找到,而buddy system就是会自动将相邻的页框合并成连续的大空间的一种机制。

Slab allocator:slab分配器避免内存内碎片。Linux内核分配内存的方式是按照页框(默认4k大小)进行的。由于页框大小是4k,而在Linux系统中有很多信息打开时都用不了一个页框,如inode和进程描述信息,slab allocator的基本原理是按照预先固定的大小,将页框分配成特定结构大小存储小数据并且不会销毁页框而是重复使用,随时用随时申请。解决内存内碎片问题。

PS:memcached分配内存也是如此

Memcached内存分配机制

1)slab划分内存空间

Memcached默认情况下采用了slab allocator的机制分配、管理内存。memcache解决的最大的一个问题就是内存多次读取时内存碎片问题。内存碎片分为内存内部碎片和内存外部碎片。一般是指在外部碎片中出现了不连续的细小内存片段,不能够被进程利用。因为不连续,不能组合成大的连续空间,导致这部分空间很可惜的浪费了。Slab Allocator就是为解决内存碎片问题而诞生的。

Memcached启动进程的时候就按照预先设定好的大小(默认是64mb),在内存开辟出一段连续的内存空间,之后再将这段内存空间分成不同的片段。就是将Memcached分配的内存预先划分为一系列slab,每个slab只负责一定范围内的数据存储。其实就是在slab这个空间内把内存分成了尺寸相同的Chunk,如下图:

Memcached内存分配策略

memcached分配内存是在总大小64MB(默认的)中分成一组组的slab,每一个slab有特定的大小的chunk存储单元。比如上图第一个slab内存放的都是88字节的存储单元,第二个是112字节,以此类推,一直把这64MB内存分配完。但这个64MB内存是根据数据存入而逐渐分配的使用的。

memcached默认情况下下一个slab的最大值为前一个的1.25倍。如slab1跟slab2之间,slab2每个Chunk的大小就是slab1的1.25倍,同理,slab3又是slab2的1.25倍,以此类推。但是每一个slab之间的增长因子是管理员在启动Memcached时可以设定的(参数-f)。

由上说明可以知道,memcached中Chunk是用来真正存储数据的,而slab是用来管理一组特定大小的Chunk的组。对于Chunk来说,最小可以到48字节,最大默认是1MB(通过-l参数可以调整大小)。那么memcached是如何存储数据的呢?每个slab只存储大于其上一个slab Chunk的大小并小于或者等于自己最大Chunk的数据。例如:slab 3只存储大小大于112 bytes而小于114 bytes的数据(就算内存用完,slab 3也不会存储不在此区间的key)。另外因为chunk的大小固定为slab能够存放的最大值,所以所有分配给当前slab的数据都可以被chunk存下。如果数据大小小于chunk的大小,空余的空间将会被闲置,这个是为了防止内存碎片而设计的。比如Chunk size是144byte,而存储的数据只有120byte,剩下的24byte将被闲置。目前memcached对于内存浪费目前还没有完美的解决方案,只能靠开发人员根据预先知道客户端发送的数据的大小,或者仅缓存大小相同的数据的情况下,只要使用适合数据大小的组的列表,就可以减少浪费。

2)slab内存分配方式

memcached在启动时通过-m指定最大使用内存,但是这个不会一启动就占用,是随着需要逐步分配给各slab的。 从上图可以看出,这段连续的区域就好像上面的slab1+slab2+slab3+……+slab(n),分配区域相同的构成了slab(分片组)。Slab下面可不直接就是存储区域片(就是图中的chunk)了。而是page,如果一个新的缓存数据要被存放,memcached首先选择一个合适的slab,然后查看该slab是否还有空闲的chunk,如果有则直接存放进去,如果没有则要进行申请。slab申请内存时以page为单位,所以在放入第一个数据时无论大小为多少,都会有1M大小的page被分配给该slab。申请到page后,slab会将这个page的内存按chunk的大小进行切分,这样就变成了一个chunk的数组,再从这个chunk数组中选择一个用于存储数据,在Page中才是一个个小存储单元——Chunk,一个page默认1mb,那么可以放多少个88字节单位的Chunk呢?1024*1024/88约等于11915个。

综合上面的介绍,memcached的内存分配策略就是:按slab需求分配page,各slab按需使用chunk存储。 这里有几个特点要注意:memcached是不释放已分配内存。Memcached分配出去的page不会被回收或者重新分配的。slab空闲的chunk不会借给任何其他slab使用。

3)缓存的释放策略

在缓存的清除方面,memcached不会释放已分配的内存。当已分配的内存所在的记录失效后,客户端就无法再看见该记录(invisible,透明),这段以往的内存空间,memcached自然会重复利用起来。至于过期的方式,memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过期。 这种技术被称为lazy(惰性)expiration。因此,memcached不会在过期监视上耗费CPU时间。基本不会有其他线程干预数据的生命周期。至于清空的策略等同于memhcached的默认策略——最近很少使用清空策略——也就是英文常用的LRU——Least Recently Used

LRU:从缓存中有效删除数据的原理

memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不足的情况, 此时就要使用名为Least Recently Used(LRU)机制来分配空间。 顾名思义,这是删除“最近最少使用”的记录的机制。 因此,当memcached的内存空间不足时(无法从slab class 获取到新的空间时),就从最近未被使用的记录中搜索,并将其空间分配给新的记录。 从缓存的实用角度来看,该模型十分理想。不过,有些情况下LRU机制反倒会造成麻烦。memcached启动时通过“-M”参数可以禁止LRU,如下所示:

启动时必须注意的是,小写的“-m”选项是用来指定最大内存大小的。不指定具体数值则使用默认值64MB。指定“-M”参数启动后,内存用尽时memcached会返回错误。 话说回来,memcached毕竟不是存储器,而是缓存,所以推荐使用LRU。

4)占用内存比实际使用内存要大

知道了这些以后,就可以理解为什么总内存没有被全部使用的情况下,memcached却出现了丢失缓存数据的问题了。主要问题还是因为内存其实被占用完了,因为你使用stats命令看到的bytes字段的数据为实际使用的内存大小,可以使用ps或top命令看memcached进程实际占用的内存大小。当内存满了之后就会导致memcached会使用LRU算法剔除老的key,导致数据丢失。这里提供一个生产中遇到的案例:理解memcached为什么会丢失数据?

Slabs增长因子调整

Memcached在启动时指定Growth Factor因子(通过-f选项),就可以在某种程度上控制slab之间的差异,但是差异倍数值不能小于1,默认值为1.25。如下:

这里可以看出每个slabs中chunk大小之间的倍数为1.25倍,第一个Chunk为96字节,最后一个Chunk为1M。另外我给的内存是512M,可以看出memcached创建了42个slabs,最大的slabs为1个page(我算了一下初始大概申请了50M内存左右),并不是把512M内存吃完的。

通过-f可以调整增长因子差异倍数为1.1

这里调整了增长因子为1.1倍,明显看出slabs的数量增加了很多,但是第一个Chunk还是为96字节,最后一个Chunk为1M。但初始化申请的内存应该会增大了。

调整增长因子可以在某种程度上减少内存的浪费,前提是你需要存储的key都是一些很小的key才需要把增长因此调小;或你的key都是很大,也可以调大增长因子。

memcached存一个key的开销

memcached存储一个key的开销是67-69字节(别问我怎么知道的),看结果,开启一个新实例。

stats – 查看现在存储的字节,由于是刚开启的,所以还没有存储任何数据

接下来我们存储一个1字节的数据进去。

再来存储字节的大小。

存一个key,字节数变成了68,所以68-1等67,这个也就是前面说的memcached存储一个key的开销是67字节,具体实现细节我也不知道啊。后面我又测过2个字节的key,发现key的开销为69。

stats命令查看一下

了解了memcachedkey的开销后,就可以对你存储的数据放在了哪个slabs中有一个了解,比如你存储的key是3000byte,那么就需要用着3000byte加上key的开销,然后会存放到对应的slabs中。


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (2)or分享 (0)
关于作者:

您必须 登录 才能发表评论!