Memcache技術分享:介紹、使用、存儲、算法、優化、命中率

原文地址:http://zhihuzeye.com/archives/2361php

1、memcached 介紹node

1.1 memcached 是什麼?c++

memcached 是以LiveJournal旗下Danga Interactive 公司的Brad Fitzpatric 爲首開發的一款軟件。如今已成爲mixi、hatena、Facebook、Vox、LiveJournal 等衆多服務中提升Web應用擴展性的重要因素。許多Web 應用都將數據保存到RDBMS 中,應用服務器從中讀取數據並在瀏覽器中顯示。但隨着數據量的增大、訪問的集中,就會出現RDBMS 的負擔加劇、數據庫響應惡化、網站顯示延遲等重大影響。這時就該memcached 大顯身手了。memcached 是高性能的分佈式內存緩存服務器。通常的使用目的是,經過緩存數據庫查詢結果,減小數據庫訪問次數,以提升動態Web 應用的速度、提升可擴展性。算法

內置內存存儲方式數據庫

研究memcached這個產品,首先從它的內存模型開始:咱們知道c++裏分配內存有兩種方式,預先分配和動態分配,顯然,預先分配內存會使程序比較快,可是它的缺點是不能有效利用內存,而動態分配能夠有效利用內存,可是會使程序運行效率降低,memcached的內存分配就是基於以上原理,顯然爲了得到更快的速度,有時候咱們不得不以空間換時間。瀏覽器

Memcached的高性能源於兩階段哈希(two-stage hash)結構。Memcached就像一個巨大的、存儲了不少<key,value>對的哈希表。經過key,能夠存儲或查詢任意的數據。 客戶端能夠把數據存儲在多臺memcached上。當查詢數據時,客戶端首先參考節點列表計算出key的哈希值(階段一哈希),進而選中一個節點;客戶端將請求發送給選中的節點,而後
memcached節點經過一個內部的哈希算法(階段二哈希),查找真正的數據(item)並返回給客戶端。從實現的角度看,memcached是一個非阻塞的、基於事件的服務器程序。緩存

爲了提升性能,memcached 中保存的數據都存儲在memcached 內置的內存存儲空間中。因爲數據僅存在於內存中,所以重啓memcached、重啓操做系統會致使所有數據消失。另外,內容容量達到指定值以後,就基於LRU(Least Recently Used)算法自動刪除不使用的緩存。memcached 自己是爲緩存而設計的服務器,所以並無過多考慮數據的永久性問題服務器

memcached 不互相通訊的分佈式網絡

memcached 儘管是「分佈式」緩存服務器,但服務器端並無分佈式功能。各個app

memcached 不會互相通訊以共享信息。那麼,怎樣進行分佈式呢?這徹底取決於客戶端的實現。

1.2 memcached啓動

memcached 啓動的命令在安裝目錄的bin 二級目錄下,如/home/test/app/memcahced-1.4.2/bin/memcached -p 11222 -m 128–d

經常使用的一些啓動選項介紹選項說明

-p 偵聽的端口,默認爲11211

-m 使用內存大小,默認的64m

-d 做爲daemon 在後臺啓動

-vv 用very vrebose 模式啓動,調試信息和錯誤輸出到控制檯

-l 偵聽的地址,默認爲全部能夠訪問的地址

-M 用於在內存溢出的時候,返回一個錯誤,禁止自動的移出數

據,替代的是返回一個error

-P Pid 文件存在的路徑,僅限加上-d 參數是用

-c 最大同時的鏈接數,默認爲1024

其它的一些選項,能夠經過–h 命令來進行查看

1.3 命令行訪問memcached

下面假設memcached 啓動時的-p 參數爲11311,命令操做在啓動memcached

本機首先telnet 鏈接到memcached 服務器

telnet 127.0.0.1 11311

telnet 成功以後,大概會顯示下面的信息

Trying 127.0.0.1...

Connected to localhost.localdomain (127.0.0.1).

Escape character is '^]'.

各類狀態(stats)

STAT <name> <value>\r\n

如:stats命令,則返回如下信息:

stats
STAT pid 26804
STAT uptime 182783
STAT time 1404973716
STAT version 1.4.13
STAT libevent 2.0.11-stable
STAT pointer_size 64
STAT rusage_user 2.320647
STAT rusage_system 5.411177
STAT curr_connections 34
STAT total_connections 558
STAT connection_structures 37
STAT reserved_fds 20
STAT cmd_get 127292
STAT cmd_set 60056
STAT cmd_flush 145
STAT cmd_touch 0
STAT get_hits 83811
STAT get_misses 43481
STAT delete_misses 15970
STAT delete_hits 11992
STAT incr_misses 0
STAT incr_hits 0
STAT decr_misses 0
STAT decr_hits 0
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 14300156
STAT bytes_written 11507140
STAT limit_maxbytes 134217728      #  分配給memcache的內存大小(字節)
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT expired_unfetched 16884
STAT evicted_unfetched 0
STAT bytes 609350    # 當前服務器存儲items佔用的字節數
STAT curr_items 4668    # 服務器當前存儲的items數量
STAT total_items 60056
STAT evictions 0     # 分配給memcache的空間用滿後須要刪除舊的items數,踢出。
STAT reclaimed 27160  #回收再利用,已過時的數據條目來存儲新數據。
END

memcache中指令

(1)插入指令,add,replace,set

「set」表示按照相應的<key>存儲該數據,沒有的時候增長,有的覆蓋。
「add」表示按照相應的<key>添加該數據,可是若是該<key>已經存在則會操做失敗,返回false。
「replace」表示按照相應的<key>替換數據,可是若是該<key>不存在則操做失敗,返回false。

(2)讀取指令

get <key>*\r\n

  其中,<key>* 表示一個或者多個key(以空格分開); 「\r\n」 命令頭的結束。

(3)刪除指令

delete <key> <time>\r\n

  其中, <key> 須要被刪除數據的key; <time> 客戶端但願服務器將該數據刪除的時間(unix時間或者從如今開始的秒數); 「\r\n」 命令頭的結束。

(4)退出指令,quit

二、理解memcached 的內存存儲

Memcache使用了Slab Allocator的內存分配機制:按照預先規定的大小,將分配的內存分割成特定長度的塊,以徹底解決內存碎片問題。
Memcache的存儲涉及到slab,page,chunk三個概念
1.Chunk爲固定大小的內存空間,默認爲48Byte。
2.page對應實際的物理空間,1個page爲1M。
3.一樣大小的chunk又稱爲slab。

2.一、Slab Allocation 機制:整理內存以便重複使用

最近的memcached 默認狀況下采用了名爲Slab Allocator 的機制分配、管理內存。在該機制出現之前,內存的分配是經過對全部記錄簡單地進行malloc和free 來進行的。可是,這種方式會致使內存碎片,加劇操做系統內存管理器的負擔,最壞的狀況下,會致使操做系統比memcached 進程自己還慢。Slab Allocator 就是爲解決該問題而誕生的Slab Allocation 的原理至關簡單。將分配的內存分割成各類尺寸的塊(chunk),並把尺寸相同的塊分紅組(chunk 的集合)。

並且,slab allocator 還有重複使用已分配的內存的目的。也就是說,分配到的內存不會釋放,而是重複利用。

Slab Allocation 的主要術語

Page:分配給Slab 的內存空間,默認是1MB。分配給Slab 以後根據slab 的大小切分紅chunk。

Chunk:用於緩存記錄的內存空間。

Slab Class:特定大小的chunk 的組

2.2 Slab中緩存記錄的原理

memcached 根據收到的數據的大小,選擇最適合數據大小的slab,memcached 中保存着slab 內空閒chunk 的列表,根據該列表選擇chunk,然

後將數據緩存於其中

2.3 Slab Allocator的缺點

因爲分配的是特定長度的內存,所以沒法有效利用分配的內存。例如,將100 字節的數據緩存到128 字節的chunk 中,剩餘的28字節就浪費了

對於該問題目前尚未完美的解決方案,但在文檔中記載了比較有效的解決方案。就是說,若是預先知道客戶端發送的數據的公用大小,或者僅緩存大小相同的數據的狀況下,只要使用適合數據大小的組的列表,就能夠減小浪費。可是很遺憾,如今還不能進行任何調優,只能期待之後的版本了。可是,咱們能夠調節slab class 的大小的差異。接下來講明growth factor 選項。

2.4 使用Growth Factor進行調優

memcached 在啓動時指定Growth Factor 因子(經過f 選項),就能夠在某種程度上控制slab 之間的差別。默認值爲1.25。可是,在該選項出現以前,這個因子曾經固定爲2,稱爲「powers of 2」策略。

下面是啓動後的verbose 輸出:

slab class 1: chunk size 128 perslab 8192

slab class 2: chunk size 256 perslab 4096

slab class 3: chunk size 512 perslab 2048

slab class 4: chunk size 1024 perslab 1024

slab class 5: chunk size 2048 perslab 512

slab class 6: chunk size 4096 perslab 256

slab class 7: chunk size 8192 perslab 128

slab class 8: chunk size 16384 perslab 64

slab class 9: chunk size 32768 perslab 32

slab class 10: chunk size 65536 perslab 16

slab class 11: chunk size 131072 perslab 8

slab class 12: chunk size 262144 perslab 4

slab class 13: chunk size 524288 perslab 2

可見,從128 字節的組開始,組的大小依次增大爲原來的2 倍。這樣設置的問題是,slab 之間的差異比較大,有些狀況下就至關浪費內存。所以,爲儘可能減小內存浪費,兩年前追加了growth factor 這個選項來看看如今的默認設置(f=1.25)時的輸出(篇幅所限,這裏只寫到第10 組):

slab class 1: chunk size 88 perslab 11915

slab class 2: chunk size 112 perslab 9362

slab class 3: chunk size 144 perslab 7281

slab class 4: chunk size 184 perslab 5698

slab class 5: chunk size 232 perslab 4519

slab class 6: chunk size 296 perslab 3542

slab class 7: chunk size 376 perslab 2788

slab class 8: chunk size 472 perslab 2221

slab class 9: chunk size 592 perslab 1771

slab class 10: chunk size 744 perslab 1409

可見,組間差距比因子爲2 時小得多,更適合緩存幾百字節的記錄。從上面的輸出結果來看,可能會以爲有些計算偏差,這些偏差是爲了保持字節數的對齊而故意設置的。將memcached 引入產品,或是直接使用默認值進行部署時,最好是從新計算一下數據的預期平均長度,調整growth factor,以得到最恰當的設置。內存是珍貴的資源,浪費就太惋惜了。

item佔用空間計算
*nsuffix = (uint8_t) snprintf(suffix, 40, " %d %d\r\n", flags, nbytes – 2);     

return sizeof(item) + nkey + *nsuffix + nbytes;
*nsuffix=" %d %d\r\n」的長度
若是ITEM_CAS標誌設置時,這裏有8字節的數據
完整的item長度是鍵長+值長+後綴長+item結構大小(48字節) + 8
item.length=56+key.lenght+value.length+後綴長
32位機器 item結構是32字節
64位機器 itme結構是48字節
memcache存儲的時候對key的長度有限制,php和C的最大長度都是250

3、memcached 刪除機制

memcached 是緩存,不須要永久的保存到服務器上,本章介紹memcache 的刪除機制

3.1 memcached 在數據刪除方面有效的利用資源

Memcached 不會釋放已經分配的內存,記錄過時以後,客戶端沒法再看到這一條記錄,其存儲空間就能夠利用。

Lazy Expiration

memcached 內部不會監視記錄是否過時,而是在get 時查看記錄的時間戳,檢查記錄是否過時。這種技術被稱爲lazy(惰性)expiration。所以,memcached不會在過時監視上耗費CPU 時間

3.2 LRU從緩存中有效刪除數據的原理

1.search->refcount == 0  && 已通過期的  刪除
2.tries = 50; // 最多嘗試50次    LRU隊列tail 查找 search->refcount == 0  第一個 刪除
3. tries = 50; // 最多嘗試50次    LRU隊列tail 查找search->refcount != 0 查詢時間(超過3小時)的item  第一個 刪除

memcached 會優先使用已超時的記錄的空間,但即便如此,也會發生追加新記錄時空間不足的狀況,此時就要使用名爲Least Recently Used(LRU)機制來分配空間。顧名思義,這是刪除「最近最少使用」的記錄的機制。所以,當memcached 的內存空間不足時(沒法從slab class 獲取到新的空間時),就從最近未被使用的記錄中搜索,並將其空間分配給新的記錄。從緩存的實用角度來看,該模型十分理想。不過,有些狀況下LRU 機制反倒會形成麻煩。memcached 啓動時經過「M」參數能夠禁止LRU,以下所示:

$ memcached -M –m 1024

啓動時必須注意的是,小寫的「m」選項是用來指定最大內存大小的。不指定具體數值則使用默認值64MB。

指定「M」參數啓動後,內存用盡時memcached 會返回錯誤。話說回來,memcached 畢竟不是存儲器,而是緩存,因此推薦使用LRU

4、memcached 的分佈式算法

4.1memcached的分佈式

memcached 雖然稱爲「分佈式」緩存服務器,但服務器端並無「分佈式」功能。memcached 的分佈式,則是徹底由客戶端程序庫實現的。這種分佈式是memcached 的最大特色

memcached的分佈式是什麼意思?

下面假設memcached 服務器有node1~node3 三臺,應用程序要保存鍵名爲「tokyo」、「kanagawa」、「chiba」、「saitama」、「gunma」的數據

首先向memcached 中添加「tokyo」。將「tokyo」傳給客戶端程序庫後,客戶端實現的算法就會根據「鍵」來決定保存數據的memcached 服務器。服務器選定後,即命令它保存「tokyo」及其值

一樣,「kanagawa」、「chiba」、「saitama」、「gunma」都是先選擇服務器再保接下來獲取保存的數據。獲取時也要將要獲取的鍵「tokyo」傳遞給函數庫。函數庫經過與數據保存時相同的算法,根據「鍵」選擇服務器。使用的算法相同,就能選中與保存時相同的服務器,而後發送get 命令。只要數據沒有由於某些緣由被刪除,就能得到保存的值。

這樣,將不一樣的鍵保存到不一樣的服務器上,就實現了memcached 的分佈式。memcached 服務器增多後,鍵就會分散,即便一臺memcached 服務器發生故障沒法鏈接,也不會影響其餘的緩存,系統依然能繼續運行

4.2 餘數分佈式算法

就是「根據服務器臺數的餘數進行分散」。求得鍵的整數哈希值,再除以服務器臺數,根據其他數來選擇服務器。

餘數算法的缺點

餘數計算的方法簡單,數據的分散性也至關優秀,但也有其缺點。那就是當添加或移除服務器時,緩存重組的代價至關巨大。添加服務器後,餘數就會產生鉅變,這樣就沒法獲取與保存時相同的服務器,從而影響緩存的命中。

4.3Consistent Hashing(一致哈希)

知識補充:哈希算法,即散列函數。將任意長度的二進制值映射爲較短的固定長度的二進制值,這個小的二進制值稱爲哈希值。哈希值是一段數據惟一且極其緊湊的數值表示形式。若是散列一段明文並且哪怕只更改該段落的一個字母,隨後的哈希都將產生不一樣的值。要找到散列爲同一個值的兩個不一樣的輸入,在計算上是不可能的,因此數據的哈希值能夠檢驗數據的完整性。通常用於快速查找和加密算法。(常見的有MD5,SHA-1)

Consistent Hashing的簡單說明

Consistent Hashing 以下所示:首先求出memcached 服務器(節點)的哈希值,並將其配置到0~232 的圓(continuum)上。而後用一樣的方法求出存儲數據的鍵的哈希值,並映射到圓上。而後從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務器上。若是超過232 仍然找不到服務器,就會保存到第一臺memcached 服務器上。

從上圖的狀態中添加一臺memcached 服務器。餘數分佈式算法因爲保存鍵的服務器會發生巨大變化,而影響緩存的命中率,但Consistent Hashing中,只有在continuum 上增長服務器的地點逆時針方向的第一臺服務器上的鍵會受到影響。

Consistent Hashing:添加服務器

所以,Consistent Hashing 最大限度地抑制了鍵的從新分佈。並且,有的Consistent Hashing 的實現方法還採用了虛擬節點的思想。使用通常的hash函數的話,服務器的映射地點的分佈很是不均勻。所以,使用虛擬節點的思想,爲每一個物理節點(服務器)在continuum上分配100~200 個點。這樣就能抑制分佈不均勻,最大限度地減少服務器增減時的緩存從新分佈。

經過上文中介紹的使用Consistent Hashing 算法的memcached 客戶端函數庫進行測試的結果是,由服務器臺數(n)和增長的服務器臺數(m)計算增長服務器後的命中率計算公式以下:

(1 n/(n+m)) * 100

存儲命令

<command name> <key> <flags> <exptime> <bytes>\r\n

- <command name> 是set, add,或者repalce

- <key> 是接下來的客戶端所要求儲存的數據的鍵值

- <flags> 是在取回內容時,與數據和發送塊一同保存服務器上的任意16位無符號整形(用十進制來書寫)。客戶端能夠用它做爲「位域」來存儲一些特定的信息;它對服務器是不透明的。

- <exptime> 是終止時間。若是爲0,該項永不過時(雖然它可能被刪除,以便爲其餘緩存項目騰出位置)。若是非0(Unix 時間戳或當前時刻的秒偏移),到達終止時間後,客戶端沒法再得到這項內容。

- <bytes> 是隨後的數據區塊的字節長度,不包括用於分野的「\r\n」。它能夠是0(這時後面跟隨一個空的數據區塊)。

- <data block> 是大段的8位數據,其長度由前面的命令行中的<bytes>指定。

• set 意思是「儲存此數據」

• add 意思是「儲存此數據,只在服務器*未*保留此鍵值的數據時」

• replace 意思是「儲存此數據,只在服務器*曾*保留此鍵值的數據時」

發送命令行和數據區塊之後,客戶端等待回覆,可能的回覆以下:

- "STORED\r\n"代表成功.

- "NOT_STORED\r\n"代表數據沒有被存儲,但不是由於發生錯誤。這一般意味着add或replace 命令的條件不成立,或者,項目已經位列刪除隊列(參考後文的「delete」命令)。

取回命令

get <key>*\r\n

- <key>* 表示一個或多個鍵值,由空格隔開的字串這行命令之後,客戶端的等待0個或多個項目,每項都會收到一行文本,而後跟着數據區塊。全部項目傳送完畢後,服務器發送如下字串:"END\r\n"來指示迴應完畢,服務器用如下形式發送每項內容:

VALUE <key> <flags> <bytes>\r\n

<data block>\r\n

- <key> 是所發送的鍵名

- <flags> 是存儲命令所設置的記號

- <bytes> 是隨後數據塊的長度,*不包括* 它的界定符「\r\n」

- <data block> 是發送的數據

若是在取回請求中發送了一些鍵名,而服務器沒有送回項目列表,這意味着服務器沒這些鍵名(可能由於它們從未被存儲,或者爲給其餘內容騰出空間而被刪除,或者到期,或者被已客戶端刪除)。

刪除

delete <key> <time>\r\n

- <key> 是客戶端但願服務器刪除的內容的鍵名

- <time> 是一個單位爲秒的時間(或表明直到某一刻的Unix時間),在該時間內服務器會拒絕對於此鍵名的「add」和「replace」命令。此時內容被放入delete隊列,沒法再經過「get」獲得該內容,也沒法是用「add」和「replace」命令(可是「set」命令可用)。直到指定時間,這些內容被最終從服務器的內存中完全清除。<time>參數是可選的,缺省爲0(表示內容會馬上清除,而且隨後的存儲命令都可用)。

此命令有一行迴應:- "DELETED\r\n"表示執行成功

- "NOT_FOUND\r\n"表示沒有找到這項內容

增長/減小

命令「incr」和「decr」被用來修改數據,當一些內容須要替換、增長或減小時。這些數據必須是十進制的32位無符號整新。若是不是,則看成0 來處理。修改的內容必須存在,當使用「incr」/「decr」命令修改不存在的內容時,不會被看成0處理,而是操做失敗。

客戶端發送命令行:

incr <key> <value>\r\n或decr <key> <value>\r\n

- <key> 是客戶端但願修改的內容的建名

- <value> 是客戶端要增長/減小的總數。

回覆爲如下集中情形:

- "NOT_FOUND\r\n"指示該項內容的值,不存在。

- <value>\r\n ,<value>是增長/減小。

注意"decr"命令發生下溢:若是客戶端嘗試減小的結果小於0 時,結果會是0。"incr" 命令不會發生溢出。

狀態

命令"stats" 被用於查詢服務器的運行狀態和其餘內部數據。有兩種格式。不帶參數的:

stats\r\n

這會在隨後輸出各項狀態、設定值和文檔。另外一種格式帶有一些參數:

stats <args>\r\n

經過<args>,服務器傳回各類內部數據。由於隨時可能發生變更,本文不提供參數的種類及其傳回數據。

各類狀態

受到無參數的"stats"命令後,服務器發送多行內容,以下:

STAT <name> <value>\r\n

服務器用如下一行來終止這個清單:END\r\n,在每行狀態中,<name> 是狀態的名字,<value>使狀態的數據。如下清單,是全部的狀態名稱,數據類型,和數據表明的含義。

在「類型」一列中,"32u"表示32 位無符號整型,"64u"表示64 位無符號整型,"32u:32u"表示用冒號隔開的兩個32 位無符號整型。

名稱

類型

含義

pid

32u

服務器進程ID

uptime

32u

服務器運行時間,單位秒

time

32u

服務器當前的UNIX時間

version

string

服務器的版本號

rusage_user

32u

該進程累計的用戶時間(秒:微妙)

rusage_system

32u

該進程累計的系統時間(秒:微妙)

curr_items

32u

服務器當前存儲的內容數量

total_items

32u

服務器啓動以來存儲過的內容總數

bytes

64u

服務器當前存儲內容所佔用的字節數

curr_connections

32u

鏈接數

total_connections

32u

服務器運行以來接受的鏈接總數

connection_structures

32u

服務器分配的鏈接結構的數量

cmd_get

32u

取回請求總數

cmd_set

32u

存儲請求總數

get_hits

32u

請求成功的總次數

get_misses

32u

請求失敗的總次數

bytes_read

64u

服務器從網絡讀取到的總字節數

bytes_written

64u

服務器向網絡發送的總字節數

limit_maxbytes

32u

服務器在存儲時被容許使用的字節總數

若是不想每次經過輸入stats來查看memcache狀態,能夠經過echo "stats" |nc  ip port 來查看,例如:echo "stats" | nc 127.0.0.1 9023。

5.Memcache 命中率
緩存命中率 = get_hits/cmd_get * 100% (總命中次數/總請求次數)

要提升memcached的命中率,預估咱們的value大小而且適當的調整內存頁大小和增加因子是必須的。

命中率的提高能夠經過多種方案實現.
其一,提升服務獲取的內存總量
其二,提升空間利用率,這實際上也是另外一種方式的增長內存總量
其三,應用一級別上再來一次LRU
其四,對於總體命中率,能夠採起有效的冗餘策略,減小分佈式服務時某個server發生服務抖動的狀況

6.一些注意 1. memcache已經分配的內存不會再主動清理。 2. memcache分配給某個slab的內存頁不能再分配給其餘slab。 3. flush_all不能重置memcache分配內存頁的格局,只是給全部的item置爲過時。 4. memcache最大存儲的item(key+value)大小限制爲1M,這由page大小1M限制 5.因爲memcache的分佈式是客戶端程序經過hash算法獲得的key取模來實現,不一樣的語言可能會採用不一樣的hash算法,一樣的客戶端程序也有可能使用相異的方法,所以在多語言、多模塊共用同一組memcached服務時,必定要注意在客戶端選擇相同的hash算法 6.啓動memcached時能夠經過-M參數禁止LRU替換,在內存用盡時add和set會返回失敗 7.memcached啓動時指定的是數據存儲量,沒有包括自己佔用的內存、以及爲了保存數據而設置的管理空間。所以它佔用的內存量會多於啓動時指定的內存分配量,這點須要注意。 8.memcache存儲的時候對key的長度有限制,php和C的最大長度都是250

相關文章
相關標籤/搜索