Memcache 内存分配策略和性能(使用)状态检查

前言:html

      一直在使用Memcache,可是对其内部的问题,如它内存是怎么样被使用的,使用一段时间后想看看一些状态怎么样?一直都不清楚,查了又忘记,如今整理出该篇文章,方便本身查阅。本文不涉及安装、操做。有兴趣的同窗能够查看以前写的文章和Google。git

1:参数github

memcached -h  memcached 1.4.14
-p <num> TCP端口,默认为11211,能够不设置 -U <num> UDP端口,默认为11211,0为关闭 -s <file> UNIX socket -a <mask>          access mask for UNIX socket, in octal (default: 0700) -l <addr> 监听的 IP 地址,本机能够不设置此参数 -d 以守护程序(daemon)方式运行 -u 指定用户,若是当前为 root ,须要使用此参数指定用户 -m <num> 最大内存使用,单位MB。默认64MB -M 禁止LRU策略,内存耗尽时返回错误,而不是删除项 -c <num> 最大同时链接数,默认是1024 -v                 verbose (print errors/warnings while in event loop) -vv                very verbose (also print client commands/reponses) -vvv extremely verbose (also print internal state transitions) -h 帮助信息 -i print memcached and libevent license -P <file> 保存PID到指定文件 -f <factor>        增加因子,默认1.25
-n <bytes>         初始chunk=key+suffix+value+32结构体,默认48字节 -L 启用大内存页,能够下降内存浪费,改进性能 -t <num> 线程数,默认4。因为memcached采用NIO,因此更多线程没有太多做用 -R 每一个event链接最大并发数,默认20 -C 禁用CAS命令(能够禁止版本计数,减小开销) -b                 Set the backlog queue limit (default: 1024) -B                 Binding protocol-one of ascii, binary or auto (default) -I                 调整分配slab页的大小,默认1M,最小1k到128M

 上面加粗的参数,须要重点关注,正常启动的例子:redis

启动: /usr/bin/memcached -m 64 -p 11212 -u nobody -c 2048 -f 1.1 -I 1024 -d -l 10.211.55.9 链接: telnet 10.211.55.9 11212 Trying 10.211.55.9... Connected to 10.211.55.9. Escape character is '^]'.

能够经过命令查看全部参数:stats settings算法

2:理解memcached的内存存储机制sql

      Memcached默认状况下采用了名为Slab Allocator的机制分配、管理内存。在该机制出现之前,内存的分配是经过对全部记录简单地进行malloc和free来进行的。可是,这种方式会致使内存碎片,加剧操做系统内存管理器的负担,最坏的状况下,会致使操做系统比memcached进程自己还慢。Slab Allocator就是为解决该问题而诞生的。数据库

      Slab Allocator的基本原理是按照预先规定的大小,将分配的内存以page为单位,默认状况下一个page是1M,能够经过-I参数在启动时指定,分割成各类尺寸的块(chunk), 并把尺寸相同的块分红组(chunk的集合),若是须要申请内存时,memcached会划分出一个新的page并分配给须要的slab区域。page一旦被分配在重启前不会被回收或者从新分配,以解决内存碎片问题。数组

Page缓存

分配给Slab的内存空间,默认是1MB。分配给Slab以后根据slab的大小切分红chunk。多线程

Chunk

用于缓存记录的内存空间。

Slab Class

特定大小的chunk的组。

      Memcached并非将全部大小的数据都放在一块儿的,而是预先将数据空间划分为一系列slabs,每一个slab只负责必定范围内的数据存储。memcached根据收到的数据的大小,选择最适合数据大小的slab。memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk,而后将数据缓存于其中。

      如图所示,每一个slab只存储大于其上一个slab的size并小于或者等于本身最大size的数据。例如:100字节大小的字符串会被存到slab2(88-112)中,每一个slab负责的空间是不等的,memcached默认状况下下一个slab的最大值为前一个的1.25倍,这个能够经过修改-f参数来修改增加比例。

      Slab Allocator解决了当初的内存碎片问题,但新的机制也给memcached带来了新的问题。chunk是memcached实际存放缓存数据的地方,这个大小就是管理它的slab的最大存放大小。每一个slab中的chunk大小是同样的,如上图所示slab1的chunk大小是88字节,slab2是112字节。因为分配的是特定长度的内存,所以没法有效利用分配的内存。例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了。这里须要注意的是chunk中不只仅存放缓存对象的value并且保存了缓存对象的key,expire time, flag等详细信息。因此当set 1字节的item,须要远远大于1字节的空间存放。

memcached在启动时指定 Growth Factor因子(经过-f选项), 就能够在某种程度上控制slab之间的差别。默认值为1.25。

slab的内存分配具体过程以下:

      Memcached在启动时经过-m参数指定最大使用内存,可是这个不会一启动就占用完,而是逐步分配给各slab的。若是一个新的数据要被存放,首先选择一个合适的slab,而后查看该slab是否还有空闲的chunk,若是有则直接存放进去;若是没有则要进行申请,slab申请内存时以page为单位,不管大小为多少,都会有1M大小的page被分配给该slab(该page不会被回收或者从新分配,永远都属于该slab)。申请到page后,slab会将这个page的内存按chunk的大小进行切分,这样就变成了一个chunk的数组,再从这个chunk数组中选择一个用于存储数据。若没有空闲的page的时候,则会对改slab进行LRU,而不是对整个memcache进行LRU。

以上大体讲解了memcache的内存分配策略,下面来讲明如何查看memcache的使用情况。 

3,memcache状态和性能查看

  命中率 :stats命令

按照下面的图来解读分析

 get_hits表示读取cache命中的次数,get_misses是读取失败的次数,即尝试读取不存在的缓存数据。即:

命中率=get_hits / (get_hits + get_misses) 

命中率越高说明cache起到的缓存做用越大。可是在实际使用中,这个命中率不是有效数据的命中率,有些时候get操做可能只是检查一个key存在不存在,这个时候miss也是正确的,这个命中率是从memcached启动开始全部的请求的综合值,不能反映一个时间段内的状况,因此要排查memcached的性能问题,还须要更详细的数值。可是高的命中率仍是可以反映出memcached良好的使用状况,忽然下跌的命中率可以反映大量cache丢失的发生。

② 观察各slab的items的状况:Stats items命令

主要参数说明:

outofmemory slab class为新item分配空间失败的次数。这意味着你运行时带上了-M或者移除操做失败
number 存放的数据总数
age 存放的数据中存放时间最久的数据已经存在的时间,以秒为单位
evicted 不得不从LRU中移除未过时item的次数 
evicted_time 自最后一次清除过时item起所经历的秒数,即最后被移除缓存的时间,0表示当前就有被移除,用这个来判断数据被移除的最近时间
evicted_nonzero 没有设置过时时间(默认30天),但不得不从LRU中称除该未过时的item的次数

      由于memcached的内存分配策略致使一旦memcached的总内存达到了设置的最大内存表示全部的slab可以使用的page都已经固定,这时若是还有数据放入,将致使memcached使用LRU策略剔除数据。而LRU策略不是针对全部的slabs,而是只针对新数据应该被放入的slab,例若有一个新的数据要被放入slab 3,则LRU只对slab 3进行,经过stats items就能够观察到这些剔除的状况。

注意evicted_time:并非发生了LRU就表明memcached负载过载了,由于有些时候在使用cache时会设置过时时间为0,这样缓存将被存放30天,若是内存满了还持续放入数据,而这些为过时的数据好久没有被使用,则可能被剔除。把evicted_time换算成标准时间看下是否已经达到了你能够接受的时间,例如:你认为数据被缓存了2天是你能够接受的,而最后被剔除的数据已经存放了3天以上,则能够认为这个slab的压力其实能够接受的;可是若是最后被剔除的数据只被缓存了20秒,不用考虑,这个slab已经负载太重了。

经过上面的说明能够看到当前的memcache的slab1的状态:

items有305816个,有效时间最久的是21529秒,经过LRU移除未过时的items有95336839个,经过LRU移除没有设置过时时间的未过时items有95312220个,当前就有被清除的items,启动时没有带-M参数。

③ 观察各slabs的状况:stats slabs命令

从Stats items中若是发现有异常的slab,则能够经过stats slabs查看下该slab是否是内存分配的确有问题。

主要参数说明:

属性名称 属性说明
chunk_size 当前slab每一个chunk的大小
chunk_per_page 每一个page可以存放的chunk数
total_pages 分配给当前slab的page总数,默认1个page大小1M,能够计算出该slab的大小
total_chunks 当前slab最多可以存放的chunk数,应该等于chunck_per_page * total_page
used_chunks 已经被占用的chunks总数
free_chunks 过时数据空出的chunk但尚未被使用的chunk数
free_chunks_end 新分配的可是尚未被使用的chunk数

 

 

 

 

 

 




这里须要注意total_pages 这个是当前slab总共分配大的page总数,若是没有修改page的默认大小的状况下,这个数值就是当前slab可以缓存的数据的总大小(单位为M)。若是这个slab的剔除很是严重,必定要注意这个slab的page数是否是太少了。还有一个公式:

total_chunks = used_chunks + free_chunks + free_chunks_end

另外stats slabs还有2个属性:

属性名称 属性说明

active_slabs

活动的slab总数

total_malloced

实际已经分配的总内存数,单位为byte,这个数值决定了memcached实际还能申请多少内存,若是这个值已经达到设定的上限(和stats settings中的maxbytes对比),则不会有新的page被分配。

 

④ 对象数量的统计:stats sizes

 

注意:该命令会锁定服务,暂停处理请求。该命令展现了固定chunk大小中的items的数量。也能够看出slab1(96byte)中有多少个chunks。

⑤ 查看、导出key:stats cachedump

在进入memcache中,你们都想查看cache里的key,相似redis中的keys *命令,在memcache里也能够查看,可是须要2步完成。

一是先列出items:

stats items --命令 ... ... STAT items:29:number 228 STAT items:29:age 34935 ... END

二是经过itemid取key,上面的id是29,再加上一个参数:为列出的长度,0为所有列出。

stats cachedump 29 0 --命令 ITEM 26457202 [49440 b; 1467262309 s] ... ITEM 30017977 [45992 b; 1467425702 s] ITEM 26634739 [48405 b; 1467437677 s] END --总共228个key get 26634739  取value

如何导出key呢?这里就须要经过 echo ... nc 来完成了

echo "stats cachedump 29 0" | nc 10.211.55.9 11212 >/home/zhoujy/memcache.log

在导出的时候须要注意的是:cachedump命令每次返回的数据大小只有2M,这个是memcached的代码中写死的一个数值,除非在编译前修改。

⑥ 另外一个监控工具:memcached-tool,一个perl写的工具:memcache_tool.pl

#!/usr/bin/perl
#
# memcached-tool:
#   stats/management tool for memcached.
#
# Author:
#   Brad Fitzpatrick <brad@danga.com>
#
# Contributor:
#   Andrey Niakhaichyk <andrey@niakhaichyk.org>
#
# License:
#   public domain.  I give up all rights to this
#   tool.  modify and copy at will.
#

use strict;
use IO::Socket::INET;

my $addr = shift;
my $mode = shift || "display";
my ($from, $to);

if ($mode eq "display") {
    undef $mode if @ARGV;
} elsif ($mode eq "move") {
    $from = shift;
    $to = shift;
    undef $mode if $from < 6 || $from > 17;
    undef $mode if $to   < 6 || $to   > 17;
    print STDERR "ERROR: parameters out of range\n\n" unless $mode;
} elsif ($mode eq 'dump') {
    ;
} elsif ($mode eq 'stats') {
    ;
} elsif ($mode eq 'settings') {
    ;
} elsif ($mode eq 'sizes') {
    ;
} else {
    undef $mode;
}

undef $mode if @ARGV;

die
    "Usage: memcached-tool <host[:port] | /path/to/socket> [mode]\n
       memcached-tool 10.0.0.5:11211 display    # shows slabs
       memcached-tool 10.0.0.5:11211            # same.  (default is display)
       memcached-tool 10.0.0.5:11211 stats      # shows general stats
       memcached-tool 10.0.0.5:11211 settings   # shows settings stats
       memcached-tool 10.0.0.5:11211 sizes      # shows sizes stats
       memcached-tool 10.0.0.5:11211 dump       # dumps keys and values
WARNING! sizes is a development command.
As of 1.4 it is still the only command which will lock your memcached instance for some time.
If you have many millions of stored items, it can become unresponsive for several minutes.
Run this at your own risk. It is roadmapped to either make this feature optional
or at least speed it up.
" unless $addr && $mode;


my $sock;
if ($addr =~ m:/:) {
    $sock = IO::Socket::UNIX->new(
        Peer => $addr,
    );
}
else {
    $addr .= ':11211' unless $addr =~ /:\d+$/;

    $sock = IO::Socket::INET->new(
        PeerAddr => $addr,
        Proto    => 'tcp',
    );
}
die "Couldn't connect to $addr\n" unless $sock;

if ($mode eq 'dump') {
    my %items;
    my $totalitems;

    print $sock "stats items\r\n";

    while (<$sock>) {
        last if /^END/;
        if (/^STAT items:(\d*):number (\d*)/) {
            $items{$1} = $2;
            $totalitems += $2;
        }
    }
    print STDERR "Dumping memcache contents\n";
    print STDERR "  Number of buckets: " . scalar(keys(%items)) . "\n";
    print STDERR "  Number of items  : $totalitems\n";

    foreach my $bucket (sort(keys(%items))) {
        print STDERR "Dumping bucket $bucket - " . $items{$bucket} . " total items\n";
        print $sock "stats cachedump $bucket $items{$bucket}\r\n";
        my %keyexp;
        while (<$sock>) {
            last if /^END/;
            # return format looks like this
            # ITEM foo [6 b; 1176415152 s]
            if (/^ITEM (\S+) \[.* (\d+) s\]/) {
                $keyexp{$1} = $2;
            }
        }

        foreach my $k (keys(%keyexp)) {
            print $sock "get $k\r\n";
            my $response = <$sock>;
            if ($response =~ /VALUE (\S+) (\d+) (\d+)/) {
                my $flags = $2;
                my $len = $3;
                my $val;
                read $sock, $val, $len;
                print "add $k $flags $keyexp{$k} $len\r\n$val\r\n";
                # get the END
                $_ = <$sock>;
                $_ = <$sock>;
            }
        }
    }
    exit;
}

if ($mode eq 'stats') {
    my %items;

    print $sock "stats\r\n";

    while (<$sock>) {
        last if /^END/;
        chomp;
        if (/^STAT\s+(\S*)\s+(.*)/) {
            $items{$1} = $2;
        }
    }
    printf ("#%-17s %5s %11s\n", $addr, "Field", "Value");
    foreach my $name (sort(keys(%items))) {
        printf ("%24s %12s\n", $name, $items{$name});

    }
    exit;
}

if ($mode eq 'settings') {
    my %items;

    print $sock "stats settings\r\n";

    while (<$sock>) {
        last if /^END/;
        chomp;
        if (/^STAT\s+(\S*)\s+(.*)/) {
            $items{$1} = $2;
        }
    }
    printf ("#%-17s %5s %11s\n", $addr, "Field", "Value");
    foreach my $name (sort(keys(%items))) {
        printf ("%24s %12s\n", $name, $items{$name});
    }
    exit;
}


if ($mode eq 'sizes') {
    my %items;

    print $sock "stats sizes\r\n";

    while (<$sock>) {
        last if /^END/;
        chomp;
        if (/^STAT\s+(\S*)\s+(.*)/) {
            $items{$1} = $2;
        }
    }
    printf ("#%-17s %5s %11s\n", $addr, "Size", "Count");
    foreach my $name (sort(keys(%items))) {
        printf ("%24s %12s\n", $name, $items{$name});
    }
    exit;
}

# display mode:

my %items;  # class -> { number, age, chunk_size, chunks_per_page,
#            total_pages, total_chunks, used_chunks,
#            free_chunks, free_chunks_end }

print $sock "stats items\r\n";
my $max = 0;
while (<$sock>) {
    last if /^END/;
    if (/^STAT items:(\d+):(\w+) (\d+)/) {
        $items{$1}{$2} = $3;
    }
}

print $sock "stats slabs\r\n";
while (<$sock>) {
    last if /^END/;
    if (/^STAT (\d+):(\w+) (\d+)/) {
        $items{$1}{$2} = $3;
        $max = $1;
    }
}

print "  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM\n";
foreach my $n (1..$max) {
    my $it = $items{$n};
    next if (0 == $it->{total_pages});
    my $size = $it->{chunk_size} < 1024 ?
        "$it->{chunk_size}B" :
        sprintf("%.1fK", $it->{chunk_size} / 1024.0);
    my $full = $it->{free_chunks_end} == 0 ? "yes" : " no";
    printf("%3d %8s %9ds %7d %7d %7s %8d %8d %4d\n",
           $n, $size, $it->{age}, $it->{total_pages},
           $it->{number}, $full, $it->{evicted},
           $it->{evicted_time}, $it->{outofmemory});
}
View Code
./memcached-tool 10.211.55.9:11212 --执行
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  1      96B     20157s      28  305816     yes 95431913        0    0
  2     120B     16049s      40  349520     yes 117041737        0    0
  3     152B     17574s      39  269022     yes 92679465        0    0
  4     192B     18157s      43  234823     yes 78892650        0    0
  5     240B     18722s      52  227188     yes 72908841        0    0
  6     304B     17971s      73  251777     yes 85556469        0    0
  7     384B     17881s      81  221130     yes 75596858        0    0
  8     480B     17760s      70  152880     yes 53553607        0    0
  9     600B     18167s      58  101326     yes 34647962        0    0
 10     752B     18518s      52   72488     yes 24813707        0    0
 11     944B     18903s      52   57720     yes 16707430        0    0
 12     1.2K     20475s      44   38940     yes 11592923        0    0
 13     1.4K     21220s      36   25488     yes  8232326        0    0
 14     1.8K     22710s      35   19740     yes  6232766        0    0
 15     2.3K     22027s      33   14883     yes  4952017        0    0
 16     2.8K     23139s      33   11913     yes  3822663        0    0
 17     3.5K     23495s      31    8928     yes  2817520        0    0
 18     4.4K     22611s      29    6670     yes  2168871        0    0
 19     5.5K     23652s      29    5336     yes  1636656        0    0
 20     6.9K     21245s      26    3822     yes  1334189        0    0
 21     8.7K     22794s      22    2596     yes   783620        0    0
 22    10.8K     22443s      19    1786     yes   514953        0    0
 23    13.6K     21385s      18    1350     yes   368016        0    0
 24    16.9K     23782s      16     960     yes   254782        0    0
 25    21.2K     23897s      14     672     yes   183793        0    0
 26    26.5K     27847s      13     494     yes   117535        0    0
 27    33.1K     27497s      14     420     yes    83966        0    0
 28    41.4K     28246s      14     336     yes    63703        0    0
 29    51.7K     33636s      12     228     yes    24239        0    0

解释:

含义
# slab class编号
Item_Size    chunk大小
Max_age LRU内最旧的记录的生存时间
pages 分配给Slab的页数
count Slab内的记录数、chunks数、items数、keys数
Full? Slab内是否含有空闲chunk
Evicted 从LRU中移除未过时item的次数
Evict_Time 最后被移除缓存的时间,0表示当前就有被移除
OOM -M参数?



 

 

 

 

 

 

 

 


4,总结

      实际应用Memcached时,咱们遇到的不少问题都是由于不了解其内存分配机制所致,但愿本文能让你们初步了解Memcached在内存方便的分配机制,虽然redis等一些nosql的数据库产品在不少产品中替换了memcache,可是memcache还有不少项目会依赖它,因此还得学习来解决问题,后续出现新内容会不定时更新。

5,参考文档

 Memcached内存分析、调优、集群 

 memcache内存分配、性能检测

 memcached的基础

 理解memcached的内存存储

 memcached的删除机制和发展方向

 memcached的分布式算法

 memcached的应用和兼容程序

 Memcached二三事儿

相关文章
相关标签/搜索