今天研究了一下memcache的内存分配策略,发现其和redis的确是有很大不同的,很多人知道memcache是提前预占用内存的,并且,其内存的占用是定长的,所以其内存的占用是可以被计算出来的。但是,其分配策略不是那么简单的,其合理的设置调优,也并不是那么容易的事情。下面就讲一下我的理解。以及-m参数认为无效的原因。
理解memcache的确必须先理解 slab、page、chunk 三个概念。page组成了slab,chunk组成了page,chunk是数据真实存储的位置,默认情况下,page的大小是1M,当然这个可以通过参数修改,chunk默认从96B开始,以1.25的倍数在slab区间增长。即,slab1 的chunk为96B,那slab2的所有chunk都是120B,那slab3的所有chunk就是150B,依次类推。因为page大小都是1m,那么相当于chunk的个数就确定了,如果slab1有一个page,那么就应该有10922个chunk,在内存未达到配置内存上限的时候,这个page是可以增加的,但是这里增加的单位就是page,也就是1M,比如已经存了10922个96B以下数据的key,再存一个新的,内存又没有达到配置上限,就会page增加,相应的也就再增加10922个chunk。下面具体代码看一下
启动命令
[root@localhost shell]# memcached -d -p 11212 -u memcached -m 2 -c 1024 -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
启动命令就定死了其slab的分配规则,这样就能看到数据616944以上的,一个page也就只能存一个了,也就是就会占用1M的内存,其最大是不能存储1M以上的,除非你将page的配置设置1M以上,或者数据压缩。
这个时候看一下telnet localhost 11212 stats slabs
telnet localhost 11212
stats slabs
STAT active_slabs 0
STAT total_malloced 0
END
可以看到,当其没有数据的时候,规则虽然确定,但是没有真的去分配内存的。
使用php脚本存入一个值
<?php
$memc= new Memcached();
$memc->addServer('127.0.0.1', '11212');
for($i=1;$i<=1;$i++){
$mykey = 'mykey'.$i;
$memc->set($mykey,$i);
}
$value = $memc->get($mykey);
print_r($value)
再次运行stats slabs
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 1
STAT 1:free_chunks 0
STAT 1:free_chunks_end 10921
STAT 1:mem_requested 72
STAT 1:get_hits 1
STAT 1:cmd_set 1
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 1048512
END
发现其开始分配内存,并且直接占用了1M内存(total_malloced 1048512),used_chunks 1
我们试试存储超过10922个key
<?php
$memc= new Memcached();
$memc->addServer('127.0.0.1', '11212');
for($i=1;$i<=20922;$i++){
$mykey = 'mykey'.$i;
$memc->set($mykey,$i);
//$memc->set($mykey,str_repeat('1',130));
}
$value = $memc->get($mykey);
print_r($value);
发现结果
stats slabs
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 2
STAT 1:total_chunks 21844
STAT 1:used_chunks 20922
STAT 1:free_chunks 0
STAT 1:free_chunks_end 922
STAT 1:mem_requested 1651548
STAT 1:get_hits 2
STAT 1:cmd_set 20923
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 2097024
END
page变成了2, total_malloced也为2M,我们启动的时候-m为2也就是说运行其使用的内存就是2M,那试一下超过2M
$memc= new Memcached();
$memc->addServer('127.0.0.1', '11212');
for($i=1;$i<=50922;$i++){
$mykey = 'mykey'.$i;
$memc->set($mykey,$i);
//$memc->set($mykey,str_repeat('1',130));
}
$value = $memc->get($mykey);
print_r($value);
结果:
stats slabs
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 2
STAT 1:total_chunks 21844
STAT 1:used_chunks 21844
STAT 1:free_chunks 0
STAT 1:free_chunks_end 0
STAT 1:mem_requested 1747520
STAT 1:get_hits 3
STAT 1:cmd_set 71845
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 2097024
ENDstats slabs
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 2
STAT 1:total_chunks 21844
STAT 1:used_chunks 21844
STAT 1:free_chunks 0
STAT 1:free_chunks_end 0
STAT 1:mem_requested 1747520
STAT 1:get_hits 3
STAT 1:cmd_set 71845
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 2097024
END
我们发现所有的21844个chunk全被占用,内存没有增加,这个时候肯定就有数据比淘汰了,也就是被新的key替换了。
但是如果我们测试96B以上的数据
<?php
$memc= new Memcached();
$memc->addServer('127.0.0.1', '11212');
for($i=1;$i<=1;$i++){
$mykey = 'newmykey'.$i;
//$memc->set($mykey,$i);
$memc->set($mykey,str_repeat('1',130));
}
$value = $memc->get($mykey);
print_r($value);
结果:
stats slabs
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 2
STAT 1:total_chunks 21844
STAT 1:used_chunks 21844
STAT 1:free_chunks 0
STAT 1:free_chunks_end 0
STAT 1:mem_requested 1747520
STAT 1:get_hits 3
STAT 1:cmd_set 71845
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 5:chunk_size 240
STAT 5:chunks_per_page 4369
STAT 5:total_pages 1
STAT 5:total_chunks 4369
STAT 5:used_chunks 1
STAT 5:free_chunks 0
STAT 5:free_chunks_end 4368
STAT 5:mem_requested 206
STAT 5:get_hits 1
STAT 5:cmd_set 1
STAT 5:delete_hits 0
STAT 5:incr_hits 0
STAT 5:decr_hits 0
STAT 5:cas_hits 0
STAT 5:cas_badval 0
STAT active_slabs 2
STAT total_malloced 3145584
END
其占用的slab5,内存突破了2M,又加了1M,我换了key,所以肯定是新增,slab1的没有动,还是占满的。这个数据为什么占用的比实际130B大,使用的是slab5应该是key等也需要内存的占用,这些不管,这也说明了-m参数的原则是在slab不变的情况下,不能突破限制,但是如果遇到数据有其他slab,那么他就会再增加一个page的大小,这也就是有的人说-m配置没有作用的原因,实际上不是没有作用。我们测试能否这个slab5出现两个page,我只要循环大于4369即可,因为240一个chunk这里STAT 5:chunks_per_page 4369
<?php
$memc= new Memcached();
$memc->addServer('127.0.0.1', '11212');
for($i=1;$i<=5000;$i++){
$mykey = 'newmykey'.$i;
//$memc->set($mykey,$i);
$memc->set($mykey,str_repeat('1',130));
}
$value = $memc->get($mykey);
print_r($value);
结果:
stats slabs
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 2
STAT 1:total_chunks 21844
STAT 1:used_chunks 21844
STAT 1:free_chunks 0
STAT 1:free_chunks_end 0
STAT 1:mem_requested 1747520
STAT 1:get_hits 3
STAT 1:cmd_set 71845
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 5:chunk_size 240
STAT 5:chunks_per_page 4369
STAT 5:total_pages 1
STAT 5:total_chunks 4369
STAT 5:used_chunks 4369
STAT 5:free_chunks 0
STAT 5:free_chunks_end 0
STAT 5:mem_requested 912753
STAT 5:get_hits 2
STAT 5:cmd_set 5001
STAT 5:delete_hits 0
STAT 5:incr_hits 0
STAT 5:decr_hits 0
STAT 5:cas_hits 0
STAT 5:cas_badval 0
STAT active_slabs 2
STAT total_malloced 3145584
内存没有再增加,slab5的所有chunk被占满了,page没有增加,这里也就是说,其做大就是内存达到-m限制后,再新增的slab区间,每增加一个,就加一个page的大小,比如-m 2,如果slab1已经占满了2M,那其极限内存,就是2M+42*page的大小,不会再变化。所以其策略就是,一旦slab被分配是不会再变化的