了Slab Allocator的机制分配、管理内存。在该机制出现以前,内存的分配是通过对所有记录简单地进行malloc和free来进行的。 但是,这种方式会导致内存碎片,加重操作系统内存管理器的负担,最坏的情况下, 会导致操作系统比memcached进程本身还慢。Slab Allocator就是为解决该问题而诞生的。
Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块, 以完全解决内存碎片问题。
Slab Allocation的原理相当简单。 将分配的内存分割成各种尺寸的块(chunk), 并把尺寸相同的块分成组(chunk的集合)(图1
图1 Slab Allocation的构造图而且,slab allocator还有重复使用已分配的内存的目的。 也就是说,分配到的内存不会释放,而是重复利用。
对于slab有几个术语:
page:分配给Slab的内存空间,默认是1MB。分配给Slab之后根据slab的大小切分成chunk.
chunk:用于记录缓存的内存空间
Slab class:由特定大小的chunk块组成
slab 中的缓存记录原理:
memcached根据收到的数据的大小,选择最适合数据大小的slab(图2)。 memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk, 然后将数据缓存于其中。
实际上,Slab Allocator也是有利也有弊。下面介绍一下它的缺点。
Slab Allocator解决了当初的内存碎片问题,但新的机制也给memcached带来了新的问题。
这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。 例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了(图3)。
对于该问题目前还没有完美的解决方案,但在文档中记载了比较有效的解决方案。就是说,如果预先知道客户端发送的数据的公用大小,或者仅缓存大小相同的数据的情况下, 只要使用适合数据大小的组的列表,就可以减少浪费。
但是很遗憾,现在还不能进行任何调优,只能期待以后的版本了。 但是,我们可以调节slab class的大小的差别。 接下来说明growth factor选项。
memcached在启动时指定 Growth Factor因子(通过-f选项), 就可以在某种程度上控制slab之间的差异。默认值为1.25。 但是,在该选项出现之前,这个因子曾经固定为2,称为“powers of 2”策略。
$memcached -f 1.25 (-f选项可以用来调整slab增长的因子)
查看slab的使用状态,需要下载memcached-tool脚本:
perl memcached-tool ip:port
# Item_Size Max_age 1MB_pages Count Full?
1 96 B 205654 s 1 44 no
2 120 B 1337261 s 12 163 yes
3 152 B 302966 s 3 5474 yes
4 192 B 1328685 s 5 62 yes
5 240 B 977587 s 3 158 no
6 304 B 1304414 s 8 1667 yes
7 384 B 1337261 s 5 17 no
8 480 B 1337262 s 2 16 no
9 600 B 1337261 s 3 19 no
10 752 B 1337263 s 5 9 no
11 944 B 1328488 s 4 10 no
12 1.2 kB 1328486 s 4 16 no
13 1.4 kB 1326990 s 3 8 yes
14 1.8 kB 1327343 s 4 5 no
15 2.3 kB 1326990 s 5 6 no
16 2.8 kB 364375 s 8 672 no
17 3.5 kB 1326990 s 4 6 no
18 4.4 kB 1327343 s 3 2 no
19 5.5 kB 1327310 s 11 2 no
20 6.9 kB 1327343 s 10 1 yes
21 8.7 kB 364430 s 7 413 no
22 10.8 kB 1320170 s 1 3 no
23 13.6 kB 1324719 s 2 3 no
24 16.9 kB 1327341 s 1 1 no
25 21.2 kB 891471 s 1 1 no
26 26.5 kB 1312631 s 1 7 no
27 33.1 kB 286817 s 1 1 no
28 41.4 kB 954206 s 1 1 no
29 51.7 kB 959350 s 1 1 no
30 64.7 kB 1328487 s 1 1 no
31 80.9 kB 1318688 s 1 2 no
32 101.1 kB 0 s 1 0 no
33 126.3 kB 644800 s 1 1 no
34 157.9 kB 957763 s 1 2 no
35 197.4 kB 1318686 s 1 1 no
36 246.8 kB 1312627 s 1 2 no
37 308.5 kB 1326996 s 1 2 yes
38 385.6 kB 314722 s 1 1 no
参数说明:
# | slab class编号 |
Item_Size | Chunk大小 |
Max_age | LRU内最旧的记录的生存时间 |
1MB_pages | 分配给Slab的页数 |
Count | Slab内的记录数 |
Full? | Slab内是否含有空闲chunk |
从这个脚本获得的信息对于调优非常方便,强烈推荐使用。
有关slab的详细的信息统计可以查看stats slabs命令(先要执行telnet ip port),下面给出slabs的信息:
Escape character is '^]'.
stats slabs
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 1
STAT 1:total_chunks 10922
STAT 1:used_chunks 73
STAT 1:free_chunks 3884
STAT 1:free_chunks_end 6965
STAT 1:mem_requested 26863
STAT 1:get_hits 4695884
STAT 1:cmd_set 42248
STAT 1:delete_hits 0
STAT 1:incr_hits 0
STAT 1:decr_hits 0
STAT 1:cas_hits 0
STAT 1:cas_badval 0
给出参数说明:
chunk_size:chunk的大小;
chunks_per_page:1M/chunk_size
total_pages:分配的页数
total chunks:总共的chunk数量
used_chunks:当前正在用的chunk数量
free_chunks:可以用的chunk数量
free_chunks_end :已经分配但是没有使用的chunk数量
mem_requested :实际占用的内存大小
get_hits :命中的次数
从这个slab的块可以看出每个chunk大小是96b,在其高峰时存储了10922个,接近于1m,但目前实际占用内存是26863b=0.02m,而实际分配的内存是1m,其中有6965*96/1024*1024=0.63m是无法使用的。前面说过memcache分配内存后是无法回收的,对于这0.63m已经分配但无法使用的情况是很浪费的(其他节点可能需要的chunk不是96b的,因此是不会分配到96b的slab中的),此时就造成了内存的浪费。对于上面的slab浪费还算是小的,试想如果我们线上的某一个slab分配了1g,但随着热点转移,都存储到其他节点,而原节点失效且也没有其他适合的数据块进行利用的话,那么就会出现1g的内存已经分配,但是无法使用,我们线上就出现过此种情况。
上面是进行查看slab的情况,下面的命令可以进行查看slab里面的item的情况
stats items
STAT items:1:number 41
STAT items:1:age 205654
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
STAT items:1:reclaimed 25646
STAT items:2:number 262
STAT items:2:age 1486437
STAT items:2:evicted 690
前面的编号是代表这对应slab的编号,number代表这当前slab存储的chunk个数(1号slab对应的chunk是96b)
evicted 被去除的个数,evicted_time最后被剔除的缓存对象时间
reclaimed因为超时而被回收的次数。如果evicted的个数很大,说明需要经常剔除,那么就是节点分配的内存不够,若reclaimed的个数很大,则说明经常会有超时现象,那么缓存的时间应该设置长些。
我们可以通过图形来进行监控memcache的使用状况,可以在网上下载php编写memcache图形监控工具,然后修改memcache.php的ip和端口配置,放到php服务器上进行运行,即可看到memcached的图形监控。