一、Memcache内存分配机制
了解memcached必须了解的三个单位:page、slabs、chunk。
1.1、Page
Page为内存分配的最小单位,Memcached的内存分配以page为单位,默认情况下一个page是1M,可以通过-I参数在启动时指定。如果需要申请内存 时,memcached会划分出一个新的page并分配给需要的slab区域。page一旦被分配在重启前不会被回收或者重新分配。
1.2、Slabs
Memcached并不是将所有大小的数据都放在一起的,而是预先将数据空间划分为一系列slabs,既按照不同的大小,每个slab只负责一定范围内的数据存储。如 下图,每个slab只存储大于其上一个slab的size并小于或者等于自己最大size的数据。例如:slab 3只存储大小介于137 到 224 bytes的数据。如果一个数据大小为230byte将被分配到slab 4中。从下图可以看出,每个slab负责的空间其实是不等的,memcached默认情况下下一个slab的最大值为前一个的1.25倍,这个可以通过修 改-f参数来修改增长比例。
1.3、Chunk
Chunk才是存放缓存数据的单位,Chunk是一系列固定的内存空间,这个大小就是管理它的slab的最大存放大小。例如:slab 1的所有chunk都是104byte,而slab 4的所有chunk都是280byte。chunk是memcached实际存放缓存数据的地方,因为chunk的大小固定为slab能够存放的最大值, 所以所有分配给当前slab的数据都可以被chunk存下。如果存储数据的大小小于chunk的大小,空余的空间将会被闲置,这个是为了防止内存碎片而设 计的。例如下图,chunk size是224byte,而存储的数据只有200byte,剩下的24byte将被闲置。
1.4、了解Slab的内存分配策略
Memcached在启动时通过-m指定最大使用内存,但是这个不会一启动就占用,是随着需要逐步分配给各slab的。如果一个新的缓存数据要被存放,memcached首先选择一个合适的slab,然后查看该slab是否还有空闲的chunk,如果有则直接存放进去;如 果没有则要进行申请。slab申请内存时以page为单位,所以在放入第一个数据,无论大小为多少,都会有1M大小的page被分配给该slab。申请到 page后,slab会将这个page的内存按chunk的大小进行切分,这样就变成了一个chunk的数组,在从这个chunk数组中选择一个用于存储 数据。如下图,slab 1和slab 2都分配了一个page,并按各自的大小切分成chunk数组。
综合上面的介绍:memcached的内存分配策略就是:按slab需求分配page,各slab按需使用chunk存储。Memcached分配出去的page不会被回收或者重新分配Memcached申请的内存不会被释放slab空闲的chunk不会借给任何其他slab使用。理解memcached的分别策略以后就可以理解为什么总内存没有被全部占用的情况下,memcached却出现了丢失缓存数据的问题了。
二、memcached命令及参数解释
2.1、stats
Name | Meaning |
pid | memcached 服务器的进程 id 号 |
uptime | memcached 自启动至今的时长(按秒计算) |
time | 服务器当前的 UNIX 时间戳 |
version | memcached 的版本号字符串 |
pointer_size | 默认的服务器操作系统指针尺寸(一般为 32 或 64) |
rusage_user | mcached 进程用户态的累计时长(秒:毫秒) |
rusage_system | memcached 进程内核态的累计时长(秒:毫秒) |
curr_items | memcached 当前存储的对象数量 |
total_items | memcached 自启动至今存储过的对象数量 |
bytes | emcached 当前用来存储数据所消耗的内存量(字节) |
curr_connections | memcached 当前打开的连接 |
total_connections | memcached 自启动至今打开过的连接数 |
connection_structures | memcached 分配的连接结构的数量 |
cmd_get | get 命令的总次数 |
cmd_set | set 命令的总次数 |
get_hits | get 命令命中的总次数 |
get_misses | get 命令未命中的总次数 |
delete_misses | delete 命令未命中的总次数 |
delete_hits | delete 命令命中的总次数 |
incr_misses | incr 命令未命中的总次数 |
incr_hits | incr 命令命中的总次数 |
decr_misses | decr 命令未命中的总次数 |
decr_hits | decr 命令命中的总次数 |
cas_misses | cas 命令未命中的总次数 |
cas_hits | cas 命令命中的总次数 |
cas_badval | cas 命令命中却更新失败的总次数 |
auth_cmds | memcached 接受到所有的授权命令,无论成功失败 |
auth_errors | memcached 接受到的所有失败的授权命令 |
evictions | 因 LRU 机制而被主动覆盖(删除)的对象数目 |
reclaimed | memcached 启动至今有多少次在存储数据的时候使用了过期数据的空间 |
bytes_read | memcached 从网络中读取的总数据字节数 |
bytes_written | memcached服务器发送到网络的总的字节数 |
limit_maxbytes | memcached 向网络中写入的总数据字节数 |
threads | worker 线程数量(具体请查看 doc/threads.txt) |
conn_yields | memcached 启动至今有多少次打开的连接因为内部请求数达到 -R 参数指定的限值, 一个连接的操作主动放弃让给另一个连接(不是很明白什么意思,具体可以参考 -R 参数的含义) |
2.2、stats settings
Name | Meaning |
maxbytes | memcached 可分配的最大缓存内存字节数 |
maxconns | memcached 允许的连接数最大数值 |
tcpport | TCP 监听端口 |
udpport | UDP 监听端口 |
inter | 监听的 host 地址 |
verbosity | memcached 运行信息输出级别:0 = none, 1 = some, 2 = lots |
oldest | memcached 当前存储的对象中最长的存活时长 |
evictions | 当设成 off 的时候,LRU 机制将不会启用 |
domain_socket | Unix socket 的文件路径(如果存在的话) |
umask | 创建 Unix socket 的 umask |
growth_factor | Chunk 尺寸增长因子数值 |
chunk_size | 最小的 chunk 尺寸(key+value+flags) |
num_threads | 线程数量(包括 dispatch 的) |
stat_key_prefix | Stats 命令分隔符 |
detail_enabled | 如果 yes 的话,stats 的详细信息将被开启 |
reqs_per_event | 一个事件(event)中允许的最大 IO 操作数 |
cas_enabled | 如果 no 的话,CAS 不会被启用 |
tcp_backlog | CP 监听队列(backlog)等待长度的最大值 |
auth_enabled_sasl | 是否启用 SASL 授权请求 |
2.3、stats items
Name | Meaning |
number | 当前 slab 中存储的对象数量,过期的对象不会主动被排除出去 |
age | LRU 中存活时间最长的对象的存活时长 |
evicted | 根据 LRU 原则不得不在过期之前就被删除的对象的个数 |
evicted_nonzero | 根据 LRU 原则不得不在过期之间就被删除,且有被设过过期时间的对象个数 |
evicted_time | 根据 LRU 原则不得不在过期之间就被删除,且有被设过过期时间的对象个数,用这个来做 LRU 的频率监控 |
outofmemory | 该 slab 无法为新对象分配内存空间的次数,出现这个数值意味着memcached 在运行的时候带上了-M参数或者存在 LRU 删除失败 |
tailrepairs | 这个数值表示了我们自己解决的 slab 引用泄露的次数,如果这个数值增长很多,请联系开发人员 |
reclaimed | memcached 使用一个过期数据的空间来存储数据的次数 |
2.4、stats sizes
这个stats命令将会返回存储在缓存内所有对象的尺寸和个数信息。
警告:这个命令将会锁死你的缓存!它会遍历缓存内存储的每个对象,并获取他们的尺寸。虽然这个命令速度很快,但是如果你的缓存里存储了很多对象的话,这个命令还是有可能会使你的缓存在几秒钟内无法进行任何缓存服务。
命令返回的格式为: STAT <size> <count>
这个命令会显示出,对于所有存储在你缓存中的对象,是否每32个字节就存在一个slab。你可以通过这个命令来查看,是否调整slab增长因子会节约你的内存开销。
举例来说:如果你大部分的存储对象的尺寸是小于200字节的话,创建更多小尺寸的slab会使得存储对象找到更适合它们尺寸的slab。
2.5、stats slabs
Name | Meaning |
chunk_size | 每个 chunk 占用的内存空间,一个存储对象将会寻找适合它尺寸的 chunk 来存储 |
chunks_per_page | 当前数字说明了每个 page 可以划分成多少个 chunk,一个 page 默认大小小于等于1M,Slabs 申请的内存是按 page 分配的,接着按大小划分为 chunk |
total_pages | 当前 slab 所分配到的 page 总数 |
total_chunks | 当前 slab 所分配到的 chunk 总数 |
get_hits | 当前 slab 中命中的 get 请求数 |
cmd_set | 当前 slab 接受的所有 set 命令请求数 |
delete_hits | 当前 slab 中命中的 delete 请求数 |
incr_hits | 当前 slab 中命中的 incr 请求数 |
decr_hits | 当前 slab 中命中的 decr 请求数 |
cas_hits | 当前 slab 中命中的 cas 请求数 |
cas_badval | 当前 slab 中命中但是更新失败的 cas 请求数 |
used_chunks | 已经被分配给存储对象的 chunk 数 |
free_chunks | 还未被分配给存储对象,或通过 delete 操作释放出来的 chunk |
free_chunks_end | 最近分配的 Page 尾部空闲 chunk 数 |
mem_requested | 当前 slab 中被请求用来存储对象数据的内存空间字节总数(注 1) |
active_slabs | memcached 分配的 slab 的总数 |
total_malloced | emcached 分配给所有 slab 的 pages 的内存总量 |
注1:存储对象是被存储在尺寸等于或者大于对象尺寸的slab里的。mem_requested显示了当前slab里所有存储对象总占用的内存空间。
(total_chunks * chunk_size) – mem_requested 的结果显示了有多少内存在这个slab里是被闲置的。如果你看到闲置的内存量很大, 考虑使用slab增长因子来调节slab大小,其中(used_chunks + free_chunks) * chunk_size就是用掉的chunks被分配到的内存空间,mem_requested是实际被占用掉的内存空间,两者的差值就是没有用掉的(闲置的)内存。