Memcached
原创地址:http://xenojoshua.com/2011/04/deep-in-memcached-how-it-works/
1. 几个关键概念
Page为内存分配的单位
Memcached的内存分配以page为单位,默认情况下一个page是1M,可以通过-I参数修改,最小1K,最大128M。如果需要申请内存时,memcached会划分出一个新的page并分配给需要的slab区域。page一旦被分配在memcached重启前不会被回收或者重新分配(page ressign已经从1.2.8版移除了)。
Slabs划分数据空间
Memcached并不是将所有大小的数据都放在一起的,而是预先将数据空间划分为一系列大小的slabs,每个slab只负责一定大小范围内的数据存储。每个slab只存储大于其上一个slab的size并小于或者等于自己最大size的数据。例如:slab 3只存储大小介于137 到 224 bytes的数据。如果一个数据大小为230byte的数据进行存储,它将被分配到slab 4中。每个slab负责的空间其实是不等的,memcached默认情况下下一个slab的最大值为前一个的1.25倍,这个可以通过修改-f参数来修改增长比例。
Chunk才是存放缓存数据的单位
Chunk是一系列固定的内存空间,这个大小就是管理它的slab的最大存放大小。例如:slab 1的所有chunk都是104byte,而slab 4的所有chunk都是280byte。chunk是memcached实际存放缓存数据的地方,因为chunk的大小固定为slab能够存放的最大值,所以所有分配给当前slab的数据都可以被chunk存下。如果实际的数据大小小于chunk的大小,空余的空间将会被闲置,这个是为了防止内存碎片而设计的。举例来说,如果chunk size是224byte,而存储的数据只有200byte,剩下的24byte将被闲置。此外,memcached允许配置的最小的chunk空间为48个字节(key+value+flags),通过-n参数可以调节这个数值。
2. 理解这三者之间的关系
要理解memcached是如何分配内存的就要从理解上述三个东西之间的关系开始。
page是memcached在收到内存不够的请求,并进行内存分配的单位。举例来说,slab2的所有空间都用完了,又有大小适合slab2的数据过来了,那么slab2就会向memcached请求新的内存空间,memcached就会划分一个page大小的内存量到slab2。page的默认大小是1M,这个数值可以通过参数-I来修改。
slab是memcached用来划定存储空间的大小概念,每当memcached启动的时候,它会按照-n参数配置的值(如果有的话,否则为默认值)来决定第一个slab的大小,然后根据-f参数的值来决定后续slab大小的增长速率,一个一个地决定后续的slab的大小,直到slab的大小达到设定的page大小(一般是1M)。
chunk是实际用来存储数据的内存空间,它的大小和包含它的slab的大小是一致的。当page大小的内存分配到slab的时候,slab会根据自身的大小将page大小的内存分割成 page / slabsize 个chunk。
memcached启动时候,slab创建以及chunk分配的细节可以参照下面的数据(使用-vv命令查看的详细内存分配过程)。
/usr/bin/memcached -u nobody -m 64 -p 11211 -l 127.0.0.1 -vv
slab class 1: chunk size 96 perslab 10922
slab class 2: chunk size 120 perslab 8738
slab class 3: chunk size 152 perslab 6898
slab class 4: chunk size 192 perslab 5461
slab class 5: chunk size 240 perslab 4369
slab class 6: chunk size 304 perslab 3449
slab class 7: chunk size 384 perslab 2730
slab class 8: chunk size 480 perslab 2184
slab class 9: chunk size 600 perslab 1747
slab class 10: chunk size 752 perslab 1394
slab class 11: chunk size 944 perslab 1110
slab class 12: chunk size 1184 perslab 885
slab class 13: chunk size 1480 perslab 708
slab class 14: chunk size 1856 perslab 564
slab class 15: chunk size 2320 perslab 451
slab class 16: chunk size 2904 perslab 361
slab class 17: chunk size 3632 perslab 288
slab class 18: chunk size 4544 perslab 230
slab class 19: chunk size 5680 perslab 184
slab class 20: chunk size 7104 perslab 147
slab class 21: chunk size 8880 perslab 118
slab class 22: chunk size 11104 perslab 94
slab class 23: chunk size 13880 perslab 75
slab class 24: chunk size 17352 perslab 60
slab class 25: chunk size 21696 perslab 48
slab class 26: chunk size 27120 perslab 38
slab class 27: chunk size 33904 perslab 30
slab class 28: chunk size 42384 perslab 24
slab class 29: chunk size 52984 perslab 19
slab class 30: chunk size 66232 perslab 15
slab class 31: chunk size 82792 perslab 12
slab class 32: chunk size 103496 perslab 10
slab class 33: chunk size 129376 perslab 8
slab class 34: chunk size 161720 perslab 6
slab class 35: chunk size 202152 perslab 5
slab class 36: chunk size 252696 perslab 4
slab class 37: chunk size 315872 perslab 3
slab class 38: chunk size 394840 perslab 2
slab class 39: chunk size 493552 perslab 2
slab class 40: chunk size 616944 perslab 1
slab class 41: chunk size 771184 perslab 1
slab class 42: chunk size 1048576 perslab 1
3. 举个例子来分析
首先,是memcached启动时候的情况:
商人A很有钱,他有100个大小一摸一样的仓库(100M的memcached服务器,每个page大小1M,就是一个仓库)。商人A根据自己的商品尺寸,将自己的仓库分成了42种(42个slab),定义为最小一种的仓库是专门用来存放尺寸为96的货物的(slab1大小为96个字节),然后每种仓库存放的货物大小都是之前一种的1.25倍(增长因子-f为1.25)。商人预先将42个仓库按照预定义的42种货物大小整理、装修了下(memcached启动时候的42个slab预分配、chunk分割)。1号仓库(slab1)中有10922个(1M * 1024 * 1024 / 96)货物存储空间(chunk),后续的仓库类型的装修、空间分配都以此类推。
其次,来看下slab满了的时候的情况:
商人A进了一批尺寸是150的货物,共6899个。货物按大小分配,进入3号仓库( slab3)。因为3号仓库是仓库类型3,其大小只有6898个位置(6898个chunk),6898个货物被安置到仓库类型3(slab3)的3号仓库里去。然后还多出来一个货物没地方放,商人就安排了一个新的仓库装修成仓库类型3(1M的空间分配给slab3,大小为152个字节,含6898个chunk),然后将多余的一个货物放入到新的仓库里。
这个例子看过以后,相信大家都已经很明白前述的三个概念之间的关系以及memcached是如何分配内存空间的了。
4. memcached里的内存浪费
读过上文之后大家应该很明白memcached的内存分配方式了。memcached这样分配内存的好处是不会存在内存碎片,但是坏处也很明显,就是内存的浪费。就拿前面的商人例子来说,如果遇到一种极端的情况,所有的货物进来的都是121个字节的大小,那么按逻辑他们都会被分到slab3里面去,也就是分到大小是152的slab里,也就是说每塞进一个对象,就会有31个字节的内存空间被浪费掉了。
5. memcached的数据回收机制
memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过期。 这种技术被称为lazy(惰性)expiration。因此,memcached不会在过期监视上耗费CPU时间。如果某一个item在memcached里过期了,这个东西并不会被删除,而是客户端无法再看见该记录(invisible,透明), 其存储空间即可重复使用。一般情况下memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不足的情况, 此时就要使用名为 Least Recently Used(LRU)机制来分配空间。 顾名思义,这是删除“最近最少使用”的记录的机制。 因此,当memcached的内存空间不足时(无法从slab class 获取到新的空间时),就从最近未被使用的记录中搜索,并将其空间分配给新的记录。
以上,主要是memcached的内存分配利用的一些经验。当然,memcached的配置、调优、监控在这篇文章里是没有涉及的,以后有机会的话会补上。