声明:
1、本文需要对memcache有一定了解
2、测试用memcached版本1.4.5
3、错误之处恳请指正!
很早就听说用memcached缓存数据,在分配内存未用尽时偶尔也会出现踢出未过期数据的问题。对此一直不是很理解,由于项目遇到应用选择问题,所以专门对其进行了些测试和研究。
首先简单介绍几个名词,了解过memcache原理的应该都不陌生:
chunk 数据存放的最小单元,不同slab中的chunk大小不同
page 内存页,每页有若干相同大小的chunk
slab 数据区,每区有一个以上的page(动态分配)
存储原理
当开启一个memcached服务时,它会初始化若干个slab以及对应的chunk,但是此时仅是结构,并没有真正分配实际内存。例如:
# /usr/local/bin/memcached -d -m 1m -u root -l 192.168.1.60 -p 12000 -c 256 -f 1.25 -vv
需要注意的几个参数:
-m 1m:为该守护分配1M存储空间
-f 1.25:不同slab中chunk大小的增长幅度,默认为1.25倍
-vv:测试用,方便查看分配情况
启动后会显示如下信息:
slab class 1: chunk size 80 perslab 13107 ----》slab1中chunk大小为80字节,每页有13107个chunk
slab class 2: chunk size 104 perslab 10082 ---》slab2中chunk大小约为80*1.25字节(该处会考虑到数据对齐,可能不是严格的倍数值),每页有10082个chunk
slab class 3: chunk size 136 perslab 7710 ---》slab3中chunk大小约为104*1.25
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
slab class 11: chunk size 872 perslab 1202
slab class 12: chunk size 1096 perslab 956
slab class 13: chunk size 1376 perslab 762
slab class 14: chunk size 1720 perslab 609
slab class 15: chunk size 2152 perslab 487
slab class 16: chunk size 2696 perslab 388
slab class 17: chunk size 3376 perslab 310
slab class 18: chunk size 4224 perslab 248
slab class 19: chunk size 5280 perslab 198
slab class 20: chunk size 6600 perslab 158
slab class 21: chunk size 8256 perslab 127
slab class 22: chunk size 10320 perslab 101
slab class 23: chunk size 12904 perslab 81
slab class 24: chunk size 16136 perslab 64
slab class 25: chunk size 20176 perslab 51
slab class 26: chunk size 25224 perslab 41
slab class 27: chunk size 31536 perslab 33
slab class 28: chunk size 39424 perslab 26
slab class 29: chunk size 49280 perslab 21
slab class 30: chunk size 61600 perslab 17
slab class 31: chunk size 77000 perslab 13
slab class 32: chunk size 96256 perslab 10
slab class 33: chunk size 120320 perslab 8
slab class 34: chunk size 150400 perslab 6
slab class 35: chunk size 188000 perslab 5
slab class 36: chunk size 235000 perslab 4
slab class 37: chunk size 293752 perslab 3
slab class 38: chunk size 367192 perslab 2
slab class 39: chunk size 458992 perslab 2
slab class 40: chunk size 573744 perslab 1
slab class 41: chunk size 717184 perslab 1
slab class 42: chunk size 1048576 perslab 1
此时并没有开放实际存储数据的内存块,也就是之前分配的1M还没有用到。但是注意,此时memcached会为每一类slab发放一个page的权限(默认每页为1M),也就是说每个slab都能存入1M的数据,所以实际内存占用将不仅仅是之前分配的1M!当然该例子这是比较极端情况。
有数据进来时,memcached会先判断该数据的大小符合哪一类slab从而将其存入,例如40B的话会选择存入slab1;500B的话则会存入slab9。
假设为该守护分配足够大的内存空间,当slab1的第一个page被填满,则程序会动态分配一个新page,其他页也如此。
假设分配的内存已被用尽,其他新的slab则只能使用初始化时分配的那唯一一个page,而不能动态分配新的page。新数据只能通过LRU算法覆盖该slab中已存在的chunk,前提是chunk中数据设置了过期时间,否则不对其进行LRU而优先覆盖(貌似是这样的)。
将1M数据写满后,进telnet用stats slabs查看结果:
# telnet 192.168.1.60 12000
Trying 192.168.1.60...
Connected to localhost (192.168.1.60).
Escape character is '^]'.
stats slabs
STAT 1:chunk_size 80 ---------> 该slab中每块chunk的大小
STAT 1:chunks_per_page 13107 -> 每页包含的chunk数量
STAT 1:total_pages 1 ---------> 该slab目前所用的页数
STAT 1:total_chunks 13107 ----> 该slab总共的chunks
STAT 1:used_chunks 13107 -----> 现在已使用有效的chunks
STAT 1:free_chunks 0 ---------> 过期释放掉的chunks
STAT 1:free_chunks_end 0 -----> 剩余未使用的chunks
STAT 1:mem_requested 722882
STAT 1:get_hits 0
STAT 1:cmd_set 13107
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
STAT active_slabs 1
STAT total_malloced 1048560
END
最后需要注意的几点:
1、存入数据时会优先使用已过期数据的空间,再使用未被使用的空间,即free_chunks会比free_chunks_end先用掉
2、存入数据时会优先使用未设置过期时间的数据空间,不再对其使用LRU算法(当成已过期数据?)
3、这里的LRU算法指的是“最近最久未被使用”,而不是“最近最少使用”
4、memcache确实只适合用来作为缓存,万不可将其用于可高可靠性需求的场合