到这里memcached的初步使用我们已经没问题了,但是了解一些它内部的机制还是十分必要的,这直接涉及到你能否把memcached给真正“用好”。
Memcached的守护进程机制使用的是Unix下的daemon,Socket则使用了非阻塞(non-blocked)高性能的NIO,事件处理上大家都已经知道了,是基于libevent,支持异步的事件处理。
最主要的是要知道它的内存管理机制,使用如下命令启动memcached:
liyd@ubuntu:~$ memcached -d -m256 -p11211 -u liyd
这里我们分配了256M的内存给memcached,那么memcached又是怎么样来分配内存的呢?先看下图:
Memcached在分配内存时是以Page为单位的,默认情况下一个Page是1M,内部是一个个chunk,当chunk的大小等于Page大小时也就是Memcached所能存储的最大数据大小了,可以在启动时通过-l来指定它,最大可以支持128M。
Memcached并不是将所有大小的数据都存放在一起的,而是将内存空间划分为一个个的slab,每个slab只负责一定范围内的数据。上图中,slab1只负责96bytes的数据,slab2负责120bytes的数据。
在存储数据时,如果这个item对应的slab还没有创建则申请一个page的内存,将这个page按照所在slab中chunk的大小进行分割,然后将item存入。
如果已经创建存在了,判断对应的slab是否用完,没用完直接存储。
如果对应的slab已经用完了,看内存是否用完,没用完会申请一个新的page进行分割存储,用完了则直接进行LRU。
那么我们怎么样来查看各个slab的状况及里面的chunk大小呢?
在前面的启动参数中我们发现有-v –vv -vvv三个选项,一般我们用的最多的是-vv:
liyd@ubuntu:~$ memcached -d -m256 -p11211 -u liyd -vv liyd@ubuntu:~$ 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 <26 server listening (auto-negotiate) <27 send buffer was 212992, now 268435456 <28 send buffer was 212992, now 268435456 <27 server listening (udp) <28 server listening (udp) <27 server listening (udp) <28 server listening (udp) <27 server listening (udp) <28 server listening (udp) <27 server listening (udp) <28 server listening (udp)
我们看到,一共有42个slab,第一个slab中chunk大小为96bytes,第二个为120bytes,第三个为152bytes,每个slab中chunk的大小都不一样,这个chunk就是memcached具体存储数据的地方。
Memcached通过指定的成长因子(-f指定,默认1.25倍)来决定每个slab中chunk增长的范围,第一个slab的大小可以通过-n来设定。
当数据进来时Memcached会选择一个大于等于最接近的slab来进行存储。例如当item大小为95时将存储到chunk为96bytes的slab1,item大小为97时则会存储到chunk大小为120的slab2.
这样分配的好处是速度快,避免大量重复的初始化和清理操作,有效的避免了内存碎片的问题,但内存利用率上会有所浪费。
另外Memcached是懒检测机制,当存储在内存中的对象过期甚至是flush_all时,它并不会做检查或删除操作,只有在get时才检查数据对象是否应该删除。
删除数据时,Memcached同样是懒删除机制,只在对应的数据对象上做删除标识并不回收内存,在下次分配时直接覆盖使用。
了解了Memcached的内存分配机制,如何进行调优是不是自然而然的就明白了?
应该尽量的根据实际情况来设定slab的chunk的初始大小和增长因子,尽量减少内存的浪费。在某些情况下数据的长度都会集中在一个区域,如session。甚至会有定长的情况,如数据统计等。
还有一个重要调优的地方就是提高缓存命中率了,这个没有固定的方法,还得具体场景做具体业务分析,需要注意的就是,Memcached中LRU的操作是基于slab而非全局,分析时最好考虑这一点,这也就是有时候内存还没用完但数据却被回收了的原因。
我们也可以借助类似memcached-tool这类对memcache的状态性能分析工具来更直观的查看memcache内部的状态,但是功能上也比较有限,就不细讲了,主要就是以下几个命令:
#memcached-tool
#Usage: memcached-tool <host[:port]> [mode]
memcached-tool 127.0.0.1:11211 display # shows slabs
memcached-tool 127.0.0.1:11211 # same. (default is display)
memcached-tool 127.0.0.1:11211 stats # shows general stats
memcached-tool 127.0.0.1:11211 dump # dumps keys and value
现在我们再回过头去看Memcached的stats命令,是不是就很有用了?这里贴上常用的一些参数说明。
stats统计项:
pid Memcached进程ID uptime Memcached运行时间,单位:秒 time Memcached当前的UNIX时间 version Memcached的版本号 rusage_user 该进程累计的用户时间,单位:秒 rusage_system 该进程累计的系统时间,单位:秒 curr_connections 当前连接数量 total_connections Memcached运行以来接受的连接总数 connection_structures Memcached分配的连接结构的数量 cmd_get 查询请求总数 get_hits 查询成功获取数据的总次数 get_misses 查询成功未获取到数据的总次数 cmd_set 存储(添加/更新)请求总数 bytes Memcached当前存储内容所占用字节数 bytes_read Memcached从网络读取到的总字节数 bytes_written Memcached向网络发送的总字节数 limit_maxbytes Memcached在存储时被允许使用的字节总数 curr_items Memcached当前存储的内容数量 total_items Memcached启动以来存储过的内容总数 evictions LRU释放对象数,用来释放内存
stats slabs区块统计:
chunk_size chunk大小,byte chunks_per_page 每个page的chunk数量
total_pages page数量
total_chunks chunk数量*page数量
get_hits get命中数
cmd_set set数
delete_hits delete命中数
incr_hits incr命中数
decr_hits decr命中数
cas_hits cas命中数
cas_badval cas数据类型错误数
used_chunks 已被分配的chunk数
free_chunks 剩余chunk数
free_chunks_end 分完page浪费chunk数
mem_requested 请求存储的字节数
active_slabs slab数量
total_malloced 总内存数量
被浪费内存数=(total_chunks * chunk_size) - mem_requested,如果太大,则需要调整factor
stats items数据项统计:
number 该slab中对象数,不包含过期对象 age LRU队列中最老对象的过期时间 evicted LRU释放对象数 evicted_nonzero 设置了非0时间的LRU释放对象数 evicted_time 最后一次LRU秒数,监控频率 outofmemory 不能存储对象次数,使用-M会报错 tailrepairs 修复slabs次数 reclaimed 使用过期对象空间存储对象次数
stats settings查看设置:
maxbytes 最大字节数限制,0无限制 maxconns 允许最大连接数 tcpport TCP端口 udpport UDP端口 verbosity 日志0=none,1=som,2=lots oldest 最老对象过期时间 evictions on/off,是否禁用LRU domain_socket socket的domain umask 创建Socket时的umask growth_factor 增长因子 chunk_size key+value+flags大小 num_threads 线程数,可以通过-t设置,默认4 stat_key_prefix stats分隔符 detail_enabled yes/no,显示stats细节信息 reqs_per_event 最大IO吞吐量(每event) cas_enabled yes/no,是否启用CAS,-C禁用 tcp_backlog TCP监控日志 auth_enabled_sasl yes/no,是否启用SASL验证
原文地址:Memcached深入分析及内存调优