今天研究了一下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。下面具体代码看一下

启动命令

  1. [root@localhost shell]# memcached -d -p 11212 -u memcached -m 2 -c 1024 -vv
  2. slab class 1: chunk size 96 perslab 10922
  3. slab class 2: chunk size 120 perslab 8738
  4. slab class 3: chunk size 152 perslab 6898
  5. slab class 4: chunk size 192 perslab 5461
  6. slab class 5: chunk size 240 perslab 4369
  7. slab class 6: chunk size 304 perslab 3449
  8. slab class 7: chunk size 384 perslab 2730
  9. slab class 8: chunk size 480 perslab 2184
  10. slab class 9: chunk size 600 perslab 1747
  11. slab class 10: chunk size 752 perslab 1394
  12. slab class 11: chunk size 944 perslab 1110
  13. slab class 12: chunk size 1184 perslab 885
  14. slab class 13: chunk size 1480 perslab 708
  15. slab class 14: chunk size 1856 perslab 564
  16. slab class 15: chunk size 2320 perslab 451
  17. slab class 16: chunk size 2904 perslab 361
  18. slab class 17: chunk size 3632 perslab 288
  19. slab class 18: chunk size 4544 perslab 230
  20. slab class 19: chunk size 5680 perslab 184
  21. slab class 20: chunk size 7104 perslab 147
  22. slab class 21: chunk size 8880 perslab 118
  23. slab class 22: chunk size 11104 perslab 94
  24. slab class 23: chunk size 13880 perslab 75
  25. slab class 24: chunk size 17352 perslab 60
  26. slab class 25: chunk size 21696 perslab 48
  27. slab class 26: chunk size 27120 perslab 38
  28. slab class 27: chunk size 33904 perslab 30
  29. slab class 28: chunk size 42384 perslab 24
  30. slab class 29: chunk size 52984 perslab 19
  31. slab class 30: chunk size 66232 perslab 15
  32. slab class 31: chunk size 82792 perslab 12
  33. slab class 32: chunk size 103496 perslab 10
  34. slab class 33: chunk size 129376 perslab 8
  35. slab class 34: chunk size 161720 perslab 6
  36. slab class 35: chunk size 202152 perslab 5
  37. slab class 36: chunk size 252696 perslab 4
  38. slab class 37: chunk size 315872 perslab 3
  39. slab class 38: chunk size 394840 perslab 2
  40. slab class 39: chunk size 493552 perslab 2
  41. slab class 40: chunk size 616944 perslab 1
  42. slab class 41: chunk size 771184 perslab 1
  43. slab class 42: chunk size 1048576 perslab 1

启动命令就定死了其slab的分配规则,这样就能看到数据616944以上的,一个page也就只能存一个了,也就是就会占用1M的内存,其最大是不能存储1M以上的,除非你将page的配置设置1M以上,或者数据压缩。

这个时候看一下telnet localhost 11212 stats slabs

  1. telnet localhost 11212
  2. stats slabs
  3. STAT active_slabs 0
  4. STAT total_malloced 0
  5. END

可以看到,当其没有数据的时候,规则虽然确定,但是没有真的去分配内存的。

使用php脚本存入一个值

  1. <?php
  2. $memc= new Memcached();
  3. $memc->addServer('127.0.0.1', '11212');
  4. for($i=1;$i<=1;$i++){
  5. $mykey = 'mykey'.$i;
  6. $memc->set($mykey,$i);
  7. }
  8. $value = $memc->get($mykey);
  9. print_r($value)

再次运行stats slabs

  1. stats slabs
  2. STAT 1:chunk_size 96
  3. STAT 1:chunks_per_page 10922
  4. STAT 1:total_pages 1
  5. STAT 1:total_chunks 10922
  6. STAT 1:used_chunks 1
  7. STAT 1:free_chunks 0
  8. STAT 1:free_chunks_end 10921
  9. STAT 1:mem_requested 72
  10. STAT 1:get_hits 1
  11. STAT 1:cmd_set 1
  12. STAT 1:delete_hits 0
  13. STAT 1:incr_hits 0
  14. STAT 1:decr_hits 0
  15. STAT 1:cas_hits 0
  16. STAT 1:cas_badval 0
  17. STAT active_slabs 1
  18. STAT total_malloced 1048512
  19. END

发现其开始分配内存,并且直接占用了1M内存(total_malloced 1048512),used_chunks 1

我们试试存储超过10922个key

  1. <?php
  2. $memc= new Memcached();
  3. $memc->addServer('127.0.0.1', '11212');
  4. for($i=1;$i<=20922;$i++){
  5. $mykey = 'mykey'.$i;
  6. $memc->set($mykey,$i);
  7. //$memc->set($mykey,str_repeat('1',130));
  8. }
  9. $value = $memc->get($mykey);
  10. print_r($value);

发现结果

  1. stats slabs
  2. STAT 1:chunk_size 96
  3. STAT 1:chunks_per_page 10922
  4. STAT 1:total_pages 2
  5. STAT 1:total_chunks 21844
  6. STAT 1:used_chunks 20922
  7. STAT 1:free_chunks 0
  8. STAT 1:free_chunks_end 922
  9. STAT 1:mem_requested 1651548
  10. STAT 1:get_hits 2
  11. STAT 1:cmd_set 20923
  12. STAT 1:delete_hits 0
  13. STAT 1:incr_hits 0
  14. STAT 1:decr_hits 0
  15. STAT 1:cas_hits 0
  16. STAT 1:cas_badval 0
  17. STAT active_slabs 1
  18. STAT total_malloced 2097024
  19. END

page变成了2, total_malloced也为2M,我们启动的时候-m为2也就是说运行其使用的内存就是2M,那试一下超过2M

  1. $memc= new Memcached();
  2. $memc->addServer('127.0.0.1', '11212');
  3. for($i=1;$i<=50922;$i++){
  4. $mykey = 'mykey'.$i;
  5. $memc->set($mykey,$i);
  6. //$memc->set($mykey,str_repeat('1',130));
  7. }
  8. $value = $memc->get($mykey);
  9. print_r($value);

结果:

  1. stats slabs
  2. STAT 1:chunk_size 96
  3. STAT 1:chunks_per_page 10922
  4. STAT 1:total_pages 2
  5. STAT 1:total_chunks 21844
  6. STAT 1:used_chunks 21844
  7. STAT 1:free_chunks 0
  8. STAT 1:free_chunks_end 0
  9. STAT 1:mem_requested 1747520
  10. STAT 1:get_hits 3
  11. STAT 1:cmd_set 71845
  12. STAT 1:delete_hits 0
  13. STAT 1:incr_hits 0
  14. STAT 1:decr_hits 0
  15. STAT 1:cas_hits 0
  16. STAT 1:cas_badval 0
  17. STAT active_slabs 1
  18. STAT total_malloced 2097024
  19. ENDstats slabs
  20. STAT 1:chunk_size 96
  21. STAT 1:chunks_per_page 10922
  22. STAT 1:total_pages 2
  23. STAT 1:total_chunks 21844
  24. STAT 1:used_chunks 21844
  25. STAT 1:free_chunks 0
  26. STAT 1:free_chunks_end 0
  27. STAT 1:mem_requested 1747520
  28. STAT 1:get_hits 3
  29. STAT 1:cmd_set 71845
  30. STAT 1:delete_hits 0
  31. STAT 1:incr_hits 0
  32. STAT 1:decr_hits 0
  33. STAT 1:cas_hits 0
  34. STAT 1:cas_badval 0
  35. STAT active_slabs 1
  36. STAT total_malloced 2097024
  37. END

我们发现所有的21844个chunk全被占用,内存没有增加,这个时候肯定就有数据比淘汰了,也就是被新的key替换了。

但是如果我们测试96B以上的数据

  1. <?php
  2. $memc= new Memcached();
  3. $memc->addServer('127.0.0.1', '11212');
  4. for($i=1;$i<=1;$i++){
  5. $mykey = 'newmykey'.$i;
  6. //$memc->set($mykey,$i);
  7. $memc->set($mykey,str_repeat('1',130));
  8. }
  9. $value = $memc->get($mykey);
  10. print_r($value);

结果:

  1. stats slabs
  2. STAT 1:chunk_size 96
  3. STAT 1:chunks_per_page 10922
  4. STAT 1:total_pages 2
  5. STAT 1:total_chunks 21844
  6. STAT 1:used_chunks 21844
  7. STAT 1:free_chunks 0
  8. STAT 1:free_chunks_end 0
  9. STAT 1:mem_requested 1747520
  10. STAT 1:get_hits 3
  11. STAT 1:cmd_set 71845
  12. STAT 1:delete_hits 0
  13. STAT 1:incr_hits 0
  14. STAT 1:decr_hits 0
  15. STAT 1:cas_hits 0
  16. STAT 1:cas_badval 0
  17. STAT 5:chunk_size 240
  18. STAT 5:chunks_per_page 4369
  19. STAT 5:total_pages 1
  20. STAT 5:total_chunks 4369
  21. STAT 5:used_chunks 1
  22. STAT 5:free_chunks 0
  23. STAT 5:free_chunks_end 4368
  24. STAT 5:mem_requested 206
  25. STAT 5:get_hits 1
  26. STAT 5:cmd_set 1
  27. STAT 5:delete_hits 0
  28. STAT 5:incr_hits 0
  29. STAT 5:decr_hits 0
  30. STAT 5:cas_hits 0
  31. STAT 5:cas_badval 0
  32. STAT active_slabs 2
  33. STAT total_malloced 3145584
  34. 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

  1. <?php
  2. $memc= new Memcached();
  3. $memc->addServer('127.0.0.1', '11212');
  4. for($i=1;$i<=5000;$i++){
  5. $mykey = 'newmykey'.$i;
  6. //$memc->set($mykey,$i);
  7. $memc->set($mykey,str_repeat('1',130));
  8. }
  9. $value = $memc->get($mykey);
  10. print_r($value);

结果:

  1. stats slabs
  2. STAT 1:chunk_size 96
  3. STAT 1:chunks_per_page 10922
  4. STAT 1:total_pages 2
  5. STAT 1:total_chunks 21844
  6. STAT 1:used_chunks 21844
  7. STAT 1:free_chunks 0
  8. STAT 1:free_chunks_end 0
  9. STAT 1:mem_requested 1747520
  10. STAT 1:get_hits 3
  11. STAT 1:cmd_set 71845
  12. STAT 1:delete_hits 0
  13. STAT 1:incr_hits 0
  14. STAT 1:decr_hits 0
  15. STAT 1:cas_hits 0
  16. STAT 1:cas_badval 0
  17. STAT 5:chunk_size 240
  18. STAT 5:chunks_per_page 4369
  19. STAT 5:total_pages 1
  20. STAT 5:total_chunks 4369
  21. STAT 5:used_chunks 4369
  22. STAT 5:free_chunks 0
  23. STAT 5:free_chunks_end 0
  24. STAT 5:mem_requested 912753
  25. STAT 5:get_hits 2
  26. STAT 5:cmd_set 5001
  27. STAT 5:delete_hits 0
  28. STAT 5:incr_hits 0
  29. STAT 5:decr_hits 0
  30. STAT 5:cas_hits 0
  31. STAT 5:cas_badval 0
  32. STAT active_slabs 2
  33. STAT total_malloced 3145584

内存没有再增加,slab5的所有chunk被占满了,page没有增加,这里也就是说,其做大就是内存达到-m限制后,再新增的slab区间,每增加一个,就加一个page的大小,比如-m 2,如果slab1已经占满了2M,那其极限内存,就是2M+42*page的大小,不会再变化。所以其策略就是,一旦slab被分配是不会再变化的