Memcache 內存分配策略和性能(使用)狀態檢查


前言:

      一直在使用Memcache,但是對其內部的問題,如它內存是怎么樣被使用的,使用一段時間后想看看一些狀態怎么樣?一直都不清楚,查了又忘記,現在整理出該篇文章,方便自己查閱。本文不涉及安裝、操作。有興趣的同學可以查看之前寫的文章和Google。

1:參數

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

 上面加粗的參數,需要重點關注,正常啟動的例子:

啟動: /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的內存存儲機制

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM