Slab Allocation机制
memcached默认情况下采用了名为Slab Allocator的机制来分配、管理内存。 在该机制出现以前,内存的分配是通过对所有记录简单地进行malloc和free来进行的。 但是,这种方式会导致内存碎片,加重操作系统内存管理器的负担,最坏的情况下, 会导致操作系统比memcached进程本身还慢。Slab Allocator就是为解决该问题而诞生的。
slab allocator的原理:
the primary goal of the slabs subsystem in memcached was to eliminate memory fragmentation issues totally by using fixed-size memory chunks coming from a few predetermined size classes.
Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块, 以完全解决内存碎片问题。
Slab Allocation的原理相当简单。 将分配的内存分割成各种尺寸的块(chunk), 并把尺寸相同的块分成组(chunk的集合),如下图,而且,slab allocator可以重复使用已分配的内存。 也就是说,分配到的内存不会释放,而是重复利用。
系统分配给Slab的内存空间称为page,默认是1MB。分配给Slab之后根据slab的大小切分成chunk,chunk用于缓存记录,特定大小的chunk组称为slab class。
memcached根据收到的数据的大小,选择最适合数据大小的slab,如下图。memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk, 然后将数据缓存于其中。
Slab Allocator的缺点
从slab的原理就知道,内存空间还是不能100%的得到利用,比如对于一个100 bytes的数据,slab会选择最合适的chunk大小为112bytes,这回造成12bytes的内存空间浪费。
对于这个缺点文档里有一个说明:
The most efficient way to reduce the waste is to use a list of size classes that closely matches (if that’s at all possible) common sizes of objects that the clients of this particular installation of memcached are likely to store.
就是说,如果预先知道客户端发送的数据的大小,或者仅缓存大小相同的数据, 或者只要使用适合数据大小的组的列表,就可以减少浪费。当然实际的情况是很难做到以上几点,所以也只能通过参数调整来达到尽量少的浪费内存。
使用参数进行调优
和内存优化相关的参数大致有三个,分别是chunk大小的增长因子(Growth Factor),chunk大小的初始值以及slab page的大小,下面分别详细介绍这三个参数:
Growth Factor
Growth Factor的值决定了chunk的大小按怎样的倍数进行增长,memcached在启动时可以通过-f参数指定 Growth Factor值, 就可以在某种程度上控制slab之间的差异。默认值为1.25。 但是,在该选项出现之前,这个因子曾经固定为2,称为“powers of 2”策略,下面以verbose模式启动memcached:
$ memcached -f 2 -vv
下面是输出结果:
slab class 1: chunk size 96 perslab 10922
slab class 2: chunk size 192 perslab 5461
slab class 3: chunk size 384 perslab 2730
slab class 4: chunk size 768 perslab 1365
slab class 5: chunk size 1536 perslab 682
slab class 6: chunk size 3072 perslab 341
slab class 7: chunk size 6144 perslab 170
slab class 8: chunk size 12288 perslab 85
slab class 9: chunk size 24576 perslab 42
slab class 10: chunk size 49152 perslab 21
slab class 11: chunk size 98304 perslab 10
slab class 12: chunk size 196608 perslab 5
slab class 13: chunk size 393216 perslab 2
slab class 14: chunk size 1048576 perslab 1
可见,从96字节的组开始,组的大小依次增大为原来的2倍。 这样设置的问题是,slab之间的差别比较大,有些情况下就相当浪费内存。 因此,为尽量减少内存浪费,追加了growth factor这个选项。在使用memcached时,或是直接使用默认值进行部署时, 最好是重新计算一下数据的预期平均长度,调整growth factor, 以获得最恰当的设置,避免内存的大量浪费。
chunk大小的初始值
64位机情况下,默认memcached把slab分为42类(class1~class42),在class 1中,chunk的默认大小为96字节,由于一个slab的大小是固定的1048576字节(1M),因此在class1中最多可以有10922个chunk:10922×96 + 64 = 1048576。在class1中,剩余的64字节因为不够一个chunk的大小(96byte),因此会被浪费掉。每类chunk的大小有一定的计算公式的,假定i代表分类,class i的计算公式如下:
chunk size(class i) : (default_size+item_size)*f^(i-1)+ CHUNK_ALIGN_BYTES
default_size: 默认大小为48字节,也就是memcached默认的key+value的大小为48字节,可以通过-n参数来调节其大小;
item_size: item结构体的长度,固定为48字节。default_size大小为48字节,item_size为48,因此class1的chunk大小为48+48=96字节;
CHUNK_ALIGN_BYTES是一个修正值,用来保证chunk的大小是某个值的整数倍。
下面使用-n参数将chunk的初始值大小设置为80字节:
$ memcached -n 80 -vv
输出:
slab class 1: chunk size 128 perslab 8192
slab class 2: chunk size 160 perslab 6553
slab class 3: chunk size 200 perslab 5242
slab class 4: chunk size 256 perslab 4096
slab class 5: chunk size 320 perslab 3276
slab class 6: chunk size 400 perslab 2621
slab class 7: chunk size 504 perslab 2080
slab class 8: chunk size 632 perslab 1659
slab class 9: chunk size 792 perslab 1323
slab class 10: chunk size 992 perslab 1057
slab class 11: chunk size 1240 perslab 845
page大小
$ memcached -I 524288 -vv
输出:
slab class 2: chunk size 120 perslab 4369
slab class 3: chunk size 152 perslab 3449
slab class 4: chunk size 192 perslab 2730
slab class 5: chunk size 240 perslab 2184
slab class 6: chunk size 304 perslab 1724
slab class 7: chunk size 384 perslab 1365
slab class 8: chunk size 480 perslab 1092
slab class 9: chunk size 600 perslab 873
以上介绍了memcache内存配置的三个参数,根据业务灵活的配置能大大的提高内存使用率。
查看memcached的内部状态
memcached有个名为stats的命令,使用它可以获得各种各样的信息。 执行命令的方法很多,用telnet最为简单:
$ telnet 主机名 端口号
连接到memcached之后,输入stats再按回车,即可获得包括资源利用率在内的各种信息。 此外,输入”stats slabs”或”stats items”还可以获得关于缓存记录的信息。 结束程序请输入quit。下面查看slabs的适用情况
stats slabs
结果如下:
属性说明,数据操作(get、set等)相关的属性就不说明了:
chunk_size | 当前slab chunk的大小 |
chunk_per_page | 每个page能够存放的chunk数 |
total_pages | 分配给当前slab的page总数 |
total_chunks | 当前slab最多能够存放的chunk数,应该等于chunck_per_page * total_page |
used_chunks | 已经被占用的chunks总数 |
free_chunks | 过期数据空出的chunk里还没有被使用的chunk数 |
free_chunks_end | 新分配的但是还没有被使用的chunk数 |
used_chunks, free_chunks, free_chunks_end这三个属性需要特别的关注,这三个属性可以反映出内存的适用情况。used_chunks就是字面的意思,已经使用的chunk数;free_chunks却不是所有的未被使用的chunk数,而是曾经被使用过但是因为过期而被回收的chunk数;free_chunks_end是page中从来没有被使用过的chunk数。如果free_chunks_end的数过大,表明这部分slab内存不能有效的利用;如果过小表明很快就不够用了;两种情况都需要做调整来使内存达到合理的利用。