前言: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}); }
./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,參考文檔