到这里memcached的初步使用我们已经没问题了,但是了解一些它内部的机制还是十分必要的,这直接涉及到你能否把memcached给真正“用好”。
Memcached的守护进程机制使用的是Unix下的daemon,Socket则使用了非阻塞(non-blocked)高性能的NIO,事件处理上大家都已经知道了,是基于libevent,支持异步的事件处理。
最主要的是要知道它的内存管理机制,使用如下命令启动memcached:
# /usr/local/memcached/bin/memcached -d -u root -p 11211 -vv
这里我们分配了默认的内存64M的内存给memcached,那么memcached又是怎么样来分配内存的呢?先看下图:
Memcached在分配内存时是以Page为单位的,默认情况下一个Page是1M,内部是一个个chunk,当chunk的大小等于Page大小时也就是Memcached所能存储的最大数据大小了,可以在启动时通过-l来指定它。
Memcached并不是将所有大小的数据都存放在一起的,而是将内存空间划分为一个个的slab,每个slab只负责一定范围内的数据。上图中,slab1只负责96bytes的数据,slab2负责120bytes的数据。
在存储数据时,如果这个item对应的slab还没有创建则申请一个page的内存,将这个page按照所在slab中chunk的大小进行分割,然后将item存入。
如果已经创建存在了,判断对应的slab是否用完,没用完直接存储。
如果对应的slab已经用完了,看内存是否用完,没用完会申请一个新的page进行分割存储,用完了则直接进行LRU。
那么我们怎么样来查看各个slab的状况及里面的chunk大小呢?
在前面的启动参数中我们发现有-v –vv -vvv三个选项,一般我们用的最多的是-vv:
[root@iZ25bep053pZ ~]# /usr/local/memcached/bin/memcached -d -u root -p 11211 -vv
[root@iZ25bep053pZ ~]# slab class 1: chunk size 104 perslab 10082
slab class 2: chunk size 136 perslab 7710
slab class 3: chunk size 176 perslab 5957
slab class 4: chunk size 224 perslab 4681
slab class 5: chunk size 280 perslab 3744
slab class 6: chunk size 352 perslab 2978
slab class 7: chunk size 440 perslab 2383
slab class 8: chunk size 552 perslab 1899
slab class 9: chunk size 696 perslab 1506
slab class 10: chunk size 872 perslab 1202
slab class 11: chunk size 1096 perslab 956
slab class 12: chunk size 1376 perslab 762
slab class 13: chunk size 1720 perslab 609
slab class 14: chunk size 2152 perslab 487
slab class 15: chunk size 2696 perslab 388
slab class 16: chunk size 3376 perslab 310
slab class 17: chunk size 4224 perslab 248
slab class 18: chunk size 5280 perslab 198
slab class 19: chunk size 6600 perslab 158
slab class 20: chunk size 8256 perslab 127
slab class 21: chunk size 10320 perslab 101
slab class 22: chunk size 12904 perslab 81
slab class 23: chunk size 16136 perslab 64
slab class 24: chunk size 20176 perslab 51
slab class 25: chunk size 25224 perslab 41
slab class 26: chunk size 31536 perslab 33
slab class 27: chunk size 39424 perslab 26
slab class 28: chunk size 49280 perslab 21
slab class 29: chunk size 61600 perslab 17
slab class 30: chunk size 77000 perslab 13
slab class 31: chunk size 96256 perslab 10
slab class 32: chunk size 120320 perslab 8
slab class 33: chunk size 150400 perslab 6
slab class 34: chunk size 188000 perslab 5
slab class 35: chunk size 235000 perslab 4
slab class 36: chunk size 293752 perslab 3
slab class 37: chunk size 367192 perslab 2
slab class 38: chunk size 458992 perslab 2
<6 server listening (replication)
replication: listen
<7 server listening
<8 server listening
<9 send buffer was 212992, now 268435456
<9 server listening (udp)
<10 send buffer was 212992, now 268435456
<10 server listening (udp)
我们看到,一共有38个slab,第一个slab中chunk大小为104bytes,第二个为136bytes,第三个为176bytes,每个slab中chunk的大小都不一样,这个chunk就是memcached具体存储数据的地方。
Memcached通过指定的成长因子(-f指定,默认1.25倍)来决定每个slab中chunk增长的范围,第一个slab的大小可以通过-n来设定。
当数据进来时Memcached会选择一个大于等于最接近的slab来进行存储。例如当item大小为100时将存储到chunk为104bytes的slab1,item大小为110时则会存储到chunk大小为136的slab2.
这样分配的好处是速度快,避免大量重复的初始化和清理操作,有效的避免了内存碎片的问题,但内存利用率上会有所浪费。典型的拿空间换效率,如图所示:
另外Memcached是懒检测机制,当存储在内存中的对象过期甚至是flush_all时,它并不会做检查或删除操作,只有在get时才检查数据对象是否应该删除。
删除数据时,Memcached同样是懒删除机制,只在对应的数据对象上做删除标识并不回收内存,在下次分配时直接覆盖使用。
了解了Memcached的内存分配机制,如何进行调优是不是自然而然的就明白了?
应该尽量的根据实际情况来设定slab的chunk的初始大小和增长因子,尽量减少内存的浪费。在某些情况下数据的长度都会集中在一个区域,如session。甚至会有定长的情况,如数据统计等。
还有一个重要调优的地方就是提高缓存命中率了,这个没有固定的方法,还得具体场景做具体业务分析,需要注意的就是,Memcached中LRU的操作是基于slab而非全局,分析时最好考虑这一点,这也就是有时候内存还没用完但数据却被回收了的原因。
现在我们再回过头去看Memcached的stats命令,是不是就很有用了?这里贴上常用的一些参数说明。
stats统计项:
STAT pid 22438 memcache服务器的进程ID
STAT uptime 2642 服务器已经运行的秒数
STAT time 1488435021 服务器当前的unix时间戳
STAT version 1.2.8 memcache版本
STAT pointer_size 64 当前操作系统的指针大小(32位系统一般是32bit,64就是64位操作系统)
STAT rusage_user 0.033049 进程的累计用户时间
STAT rusage_system 0.066099 进程的累计系统时间
STAT curr_items 0 当前存储的数据总数
STAT total_items 0 启动以来存储的数据总数
STAT bytes 0 当前存储占用的字节数
STAT curr_connections 5 当前打开着的连接数
STAT total_connections 8 从服务器启动以后曾经打开过的连接数
STAT connection_structures 6 服务器分配的连接构造数
STAT cmd_flush 0 flush命令请求次数
STAT cmd_get 0 get命令(获取)总请求次数
STAT cmd_set 0 set命令(保存)总请求次数
STAT get_hits 0 总命中次数
STAT get_misses 0 总未命中次数
STAT evictions 0 为获取空闲内存而删除的items数(分配给memcache的空间用满后需要删除旧的items来得到空间分配给新的items)
STAT bytes_read 30 总读取字节数(请求字节数)
STAT bytes_written 1240 总发送字节数(结果字节数)
STAT limit_maxbytes 67108864 分配给memcache的内存大小(字节)
STAT threads 2 当前线程数
STAT accepting_conns 1 服务器是否达到过最大连接
STAT listen_disabled_num 0 失效的监听数
STAT replication MASTER
STAT repcached_version 2.2
STAT repcached_qi_free 8192
END
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 使用过期对象空间存储对象次数