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二三事兒

相關文章
相關標籤/搜索