原文地址: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