- 内存如何分配给item
- 额外内存的作用
- 内存回收时机
- 单个item占用多少内存
- item被强制回收的时机
对于使用memcache的开发者来说,了解一下其内部运行机制还是很有必要的。虽然过分关注位和字节是浪费时间,但是随着经验的增长,你将受益于对底层的了解。(注:知其然知其所以然并没有坏处)
掌握了解内存的分配和释放,以及这种特殊的LRU机制是重中之重。
内存如何分配给item
在memcached服务启动的时候可以通过命令行指令-m
为数据存储分配内存。数据空间被默认拆分成n个大小为1Mb的page,然后这些page被分配到slab classes集合类中(每个page对应一个slab class),最后每个slab被切割成固定大小的chunk。
注:
- 不指定默认64M,只是代表存储上限,逐步分配,并不立即分配这么多内存;
- 分配的内存不包括服务自身占用的管理空间;
- 单个slab下的page中的chunk size相同;
- 不同slab根据增长因子(由-f指定)他们的chunk不同。有点绕呢!
一旦一个page被分配给一个slab class,则它就一直待在内存中了(除非重启服务)。所以如果你给slab class 3分配了80%的内存,那么slab class 4分配的内存就很少了(显然这种分配不合理)。最优的考虑方案就是将内存划分成许多独立的小内存区间,每个区间都有自己的计数器和LRU。
在启动memcached服务时可以通过命令-vv
来查看slab class、chunk和page分配情况,如下:
$ ./memcached -vv
slab class 1: chunk size 80 perslab 13107
slab class 2: chunk size 104 perslab 10082
slab class 3: chunk size 136 perslab 7710
slab class 4: chunk size 176 perslab 5957
slab class 5: chunk size 224 perslab 4681
slab class 6: chunk size 280 perslab 3744
slab class 7: chunk size 352 perslab 2978
slab class 8: chunk size 440 perslab 2383
slab class 9: chunk size 552 perslab 1899
slab class 10: chunk size 696 perslab 1506
[...etc...]
以上,先看slab class 1,每个chunk占用80字节,每个page包括13107个chunks。1MB-13107*80=16字节
注:1MB=1048576Bytes,显然也会造成少量内存浪费。
当你新增一条数据时,这条数据将会被存入chunk大小与它最接近的slab class中。如果你的key+value+其他数据(过期标识等)总共50字节,则它会被存进slab class 1,伴随着30字节的内存浪费。同理如果你的数据是90字节,则它会存进slab class 2,浪费14字节。
你可以通过设置增长因子-f
来自定义chunk的分配策略,但最好根据你自己的数据的需要来调整策略。切记慎用自定义,因为容易导致意想不到的惊喜。
注:memcache 会对内存进行划分不同区域大小的块,但是会默认一个最小存放数据区域块大小 size = 80/Byte 而增长因子就是以最小区域块为基础,每次递增的倍数,但是最大递增不能超过 62 个且 sizefactor < 1M。比如分配的时候-f2.0,则slab1.chunk如果是80bytes,那么slab2.chunk即为160bytes,以此类推,slabN.chunk就是2N80bytes。
不过监控下来发现,我的slab是从6开始一直到26,slab6的chunk是304而slab7是384,slab8是480,slab9是600…
384/304=1.263,480/384=1.25,600/480=1.25
我也不清楚slab6和7为什么不是1.25,理解还不深刻,衰!!!
参考Memcache-内存模型-源码分析
额外内存的作用
Memcached的chunk内存还有额外的功能。根据散列表查找item会有部分开销,同样地,每个请求链接也会使用额外的缓存。这些加起来在-m
分配的内存上限中占比应该很小,但应该记住这点开销。说不定这是压死骆驼的稻草呢。
内存回收时机
在1.5.0版本之前,过期的数据不会主动回收。如果用命令行-o modern
启动老版本,或者版本高于1.5.0,会有一个爬虫定期(理解成后台线程) 去扫描缓存数据并释放过期数据的缓存。
当你获取一条数据时,memcached查找到这条数据并比较过期时间,如果过期则释放它的内存。这便给出了一种重复利用内存的解决方案。
实际上,slab在新增item的时候先查看LRU队尾,如果队尾item过期则重用缓存,否则重新申请。参考memcache内存分配和重用机制
只有当-m分配的内存已达上限时,新的数据会强制回收相应slab中LRU队尾的item。
单个item占用多少内存
一个item的内存被其key、内部数据结构和data占用。
你可以通过在本地编译memcached源码,然后运行./sizes
命令查看一条item的占用情况。32位系统在使用CAS(启动服务时使用-C
命令开启,注意区别小写 -c
。后者指定最大同时连接数,默认是1024)协议时,每个item最多可用32字节;禁用CAS时最多40字节。64位系统的可用内存会高一点归功于可申请到更多的指针。显然在64位系统上操作更多的内存空间给我们带来了极大地灵活性。
$ ./sizes
Slab Stats 56
Thread stats 176
Global stats 108
Settings 88
Item (no cas) 32
Item (cas) 40
Libevent thread 96
Connection 320
----------------------------------------
libevent thread cumulative 11472
Thread stats cumulative 11376
item被强制回收的时机
当slab中的chunk被消耗殆尽,而且没有可用的page可供申请,就需要强制回收未过期(包括过期时间到0和未来某一时刻到期)的数据来腾出位子。
LRU怎样决定谁该被回收
当添加新的数据而占用内存已达上限时就需要强制回收内存。如果相应的slab下没有空闲的chunk和page,memcached就会扫描当前slab下的LRU队尾来寻找合适的item来回收。对于已经过期的item则重新利用其内存,如果找不到过期数据,就会强制回收掉队尾的item(即使用计数最少的那条记录)。然后更新一遍item的使用计数。