memcache的原理和命中率的總結

1       Memcache是什麼
Memcache是danga.com的一個項目,最早是爲 LiveJournal 服務的,目前全世界不少人使用這個緩存項目來構建自己大負載的網站,來分擔數據庫的壓力。
它可以應對任意多個連接,使用非阻塞的網絡IO。由於它的工作機制是在內存中開闢一塊空間,然後建立一個HashTable,Memcached自治理這些HashTable。
   
爲什麼會有Memcache和memcached兩種名稱?
實在Memcache是這個項目的名稱,而memcached是它服務器真個主程序文件名,
    
Memcache官方網站:http://www.danga.com/memcached,
2       Memcache工作原理
首先 memcached 是以守護程序方式運行於一個或多個服務器中,隨時接受客戶真個連接操縱,客戶端可以由各種語言編寫,目前已知的客戶端 API 包括 Perl/PHP/Python/Ruby/Java/C#/C 等等。客戶端在與 memcached 服務建立連接之後,接下來的事情就是存取對象了,每個被存取的對象都有一個唯一的標識符 key,存取操縱均通過這個 key 進行,保存到 memcached 中的對象實際上是放置內存中的,並不是保存在 cache 文件中的,這也是爲什麼 memcached 能夠如此高效快速的原因。留意,這些對象並不是持久的,服務停止之後,裏邊的數據就會丟失。
與很多 cache 工具類似,Memcached 的原理並不複雜。它採用了C/S的模式,在 server 端啓動服務進程,在啓動時可以指定監聽的 ip,自己的端口號,所使用的內存大小等幾個關鍵參數。一旦啓動,服務就一直處於可用狀態。Memcached 的目前版本是通過C實現,採用了單進程,單線程,異步I/O,基於事件 (event_based) 的服務方式.使用 libevent 作爲事件通知實現。多個 Server 可以協同工作,但這些 Server 之間是沒有任何通訊聯繫的,每個 Server 只是對自己的數據進行治理。Client 端通過指定 Server 真個 ip 地址(通過域名應該也可以)。需要緩存的對象或數據是以 key->value 對的形式保存在Server端。key 的值通過 hash 進行轉換,根據 hash 值把 value 傳遞到對應的具體的某個 Server 上。當需要獲取對象數據時,也根據 key 進行。首先對 key 進行 hash,通過獲得的值可以確定它被保存在了哪臺 Server 上,然後再向該 Server 發出請求。Client 端只需要知道保存 hash(key) 的值在哪臺服務器上就可以了。

        實在說到底,memcache 的工作就是在專門的機器的內存裏維護一張巨大的 hash 表,來存儲經常被讀寫的一些數組與文件,從而極大的進步網站的運行效率。


memcache 命中率

首先查看,

在linux或window上有telnet連接memcache,然後輸入stats會出現下面的命令,這些狀態的說明如下:

pid
memcache服務器的進程ID

uptime
服務器已經運行的秒數

time
服務器當前的unix時間戳

version
memcache版本

pointer_size
當前操作系統的指針大小(32位系統一般是32bit)

rusage_user
進程的累計用戶時間

rusage_system
進程的累計系統時間

curr_items
服務器當前存儲的items數量

total_items
從服務器啓動以後存儲的items總數量

bytes
當前服務器存儲items佔用的字節數

curr_connections
當前打開着的連接數

total_connections
從服務器啓動以後曾經打開過的連接數

connection_structures
服務器分配的連接構造數

cmd_get
get命令(獲取)總請求次數

cmd_set
set命令(保存)總請求次數

get_hits
總命中次數

get_misses
總未命中次數

evictions
爲獲取空閒內存而刪除的items數(分配給memcache的空間用滿後需要刪除舊的items來得到空間分配給新的items)

bytes_read
總讀取字節數(請求字節數)

bytes_written
總髮送字節數(結果字節數)

limit_maxbytes
分配給memcache的內存大小(字節)

threads
當前線程數

一、緩存命中率 = get_hits/cmd_get * 100%
二、get_misses的數字加上get_hits應該等於cmd_get
三、total_items == cmd_set == get_misses,當可用最大內存用光時,memcached就會刪掉一些內容,等式就會不成立

memcached/scripts/memcached-tool



Memcached,人所皆知的remote distribute cache(不知道的可以javaeye一下下,或者google一下下,或者baidu一下下,但是鑑於baidu的排名商業味道太濃(從最近得某某事件可以看出),所以還是建議javaeye一下下),使用起來也非常的簡單,它被用在了很多網站上面,幾乎很少有大型的網站不會使用memcached。 

曾經我也看過很多剖析memcached內部機制的文章,有一點收穫,但是看過之後又忘記了,而且沒有什麼深刻的概念,但是最近我遇到一個問題,這個問題迫使我重新來認識memcache,下面我闡述一下我遇到的問題 

問題:我有幾千萬的數據,這些數據會經常被用到,目前來看,它必須要放到memcached中,以保證訪問速度,但是我的memcached中數據經常會有丟失,而業務需求是memcached中的數據是不能丟失的。我的數據丟失的時候,memcached server的內存才使用到60%,也就是還有40%內存被嚴重的浪費掉了。但不是所有的應用都是這樣,其他應用內存浪費的就比較少。爲什麼內存才使用到60%的時候LRU就執行了呢(之所以確定是LRU執行是因爲我發現我的數據丟失的總是前面放進去的,而且這個過程中,這些數據都沒有被訪問,比如第一次訪問的時候,只能訪問第1000w條,而第300w條或者之前的數據都已經丟失了,從日誌裏看,第300w條肯定是放進去了)。 

帶着這些疑問,我開始重新審視memcached這個產品,首先從它的內存模型開始:我們知道c++裏分配內存有兩種方式,預先分配和動態分配,顯然,預先分配內存會使程序比較快,但是它的缺點是不能有效利用內存,而動態分配可以有效利用內存,但是會使程序運行效率下降,memcached的內存分配就是基於以上原理,顯然爲了獲得更快的速度,有時候我們不得不以空間換時間。 

也就是說memcached會預先分配內存,對了,memcached分配內存方式稱之爲allocator,首先,這裏有3個概念: 
1 slab 
2 page 
3 chunk 
解釋一下,一般來說一個memcahced進程會預先將自己劃分爲若干個slab,每個slab下又有若干個page,每個page下又有多個chunk,如果我們把這3個咚咚看作是object得話,這是兩個一對多得關係。再一般來說,slab得數量是有限得,幾個,十幾個,或者幾十個,這個跟進程配置得內存有關。而每個slab下得page默認情況是1m,也就是說如果一個slab佔用100m得內存得話,那麼默認情況下這個slab所擁有得page得個數就是100,而chunk就是我們得數據存放得最終地方。 

舉一個例子,我啓動一個memcached進程,佔用內存100m,再打開telnet,telnet localhost 11211,連接上memcache之後,輸入stats slabs,回車,出現如下數據: 

  1. STAT 1:chunk_size 80  
  2. STAT 1:chunks_per_page 13107  
  3. STAT 1:total_pages 1  
  4. STAT 1:total_chunks 13107  
  5. STAT 1:used_chunks 13107  
  6. STAT 1:free_chunks 0  
  7. STAT 1:free_chunks_end 13107  
  8. STAT 2:chunk_size 100  
  9. STAT 2:chunks_per_page 10485  
  10. STAT 2:total_pages 1  
  11. STAT 2:total_chunks 10485  
  12. STAT 2:used_chunks 10485  
  13. STAT 2:free_chunks 0  
  14. STAT 2:free_chunks_end 10485  
  15. STAT 3:chunk_size 128  
  16. STAT 3:chunks_per_page 8192  
  17. STAT 3:total_pages 1  
  18. STAT 3:total_chunks 8192  
  19. STAT 3:used_chunks 8192  
  20. STAT 3:free_chunks 0  
  21. STAT 3:free_chunks_end 8192  
以上就是前3個slab得詳細信息 
chunk_size表示數據存放塊得大小,chunks_per_page表示一個內存頁page中擁有得chunk得數量,total_pages表示每個slab下page得個數。total_chunks表示這個slab下chunk得總數(=total_pages * chunks_per_page),used_chunks表示該slab下已經使用得chunk得數量,free_chunks表示該slab下還可以使用得chunks數量。 

從上面得示例slab 1一共有1m得內存空間,而且現在已經被用完了,slab2也有1m得內存空間,也被用完了,slab3得情況依然如此。 而且從這3個slab中chunk得size可以看出來,第一個chunk爲80b,第二個是100b,第3個是128b,基本上後一個是前一個得1.25倍,但是這個增長情況我們是可以控制得,我們可以通過在啓動時得進程參數 –f來修改這個值,比如說 –f 1.1表示這個增長因子爲1.1,那麼第一個slab中得chunk爲80b得話,第二個slab中得chunk應該是80*1.1左右。 

解釋了這麼多也該可以看出來我遇到得問題得原因了,如果還看不出來,那我再補充關鍵的一句:memcached中新的value過來存放的地址是該value的大小決定的,value總是會被選擇存放到chunk與其最接近的一個slab中,比如上面的例子,如果我的value是80b,那麼我這所有的value總是會被存放到1號slab中,而1號slab中的free_chunks已經是0了,怎麼辦呢,如果你在啓動memcached的時候沒有追加-M(禁止LRU,這種情況下內存不夠時會out of memory),那麼memcached會把這個slab中最近最少被使用的chunk中的數據清掉,然後放上最新的數據。這就解釋了爲什麼我的內存還有40%的時候LRU就執行了,因爲我的其他slab中的chunk_size都遠大於我的value,所以我的value根本不會放到那幾個slab中,而只會放到和我的value最接近的chunk所在的slab中(而這些slab早就滿了,鬱悶了)。這就導致了我的數據被不停的覆蓋,後者覆蓋前者。 

問題找到了,解決方案還是沒有找到,因爲我的數據必須要求命中率時100%,我只能通過調整slab的增長因子和page的大小來儘量來使命中率接近100%,但是並不能100%保證命中率是100%(這話怎麼讀起來這麼彆扭呢,自我檢討一下自己的語文水平),如果您說,這種方案不行啊,因爲我的memcached server不能停啊,不要緊還有另外一個方法,就是memcached-tool,執行move命令,如:move 3 1,代表把3號slab中的一個內存頁移動到1號slab中,有人問了,這有什麼用呢,比如說我的20號slab的利用率非常低,但是page卻又很多,比如200,那麼就是200m,而2好slab經常發生LRU,明顯page不夠,我就可以move 20 2,把20號slab的一個內存頁移動到2號slab上,這樣就能更加有效的利用內存了(有人說了,一次只移動一個page,多麻煩啊?ahuaxuan說,還是寫個腳本,循環一下吧)。 

有人說不行啊,我的memcache中的數據不能丟失啊,ok,試試新浪的memcachedb吧,雖然我沒有用過,但是建議大家可以試試,它也使利用memcache協議和berkeleyDB做的(寫到這裏,我不得不佩服danga了,我覺得它最大的貢獻不是memcache server本身,而是memcache協議),據說它被用在新浪的不少應用上,包括新浪的博客。 

補充,stats slab命令可以查看memcached中slab的情況,而stats命令可以查看你的memcached的一些健康情況,比如說命中率之類的,示例如下:
  1. STAT pid 2232  
  2. STAT uptime 1348  
  3. STAT time 1218120955  
  4. STAT version 1.2.1  
  5. STAT pointer_size 32  
  6. STAT curr_items 0  
  7. STAT total_items 0  
  8. STAT bytes 0  
  9. STAT curr_connections 1  
  10. STAT total_connections 3  
  11. STAT connection_structures 2  
  12. STAT cmd_get 0  
  13. STAT cmd_set 0  
  14. STAT get_hits 0  
  15. STAT get_misses 0  
  16. STAT bytes_read 26  
  17. STAT bytes_written 16655  
  18. STAT limit_maxbytes 104857600  


從上面的數據可以看到這個memcached進程的命中率很好,get_misses低達0個,怎麼回事啊,因爲這個進程使我剛啓動的,我只用telnet連了一下,所以curr_connections爲1,而total_items爲0,因爲我沒有放數據進去,get_hits爲0,因爲我沒有調用get方法,最後的結果就是misses當然爲0,哇哦,換句話說命中率就是100%,又yy了。 

該到總結的時候了,從這篇文章裏我們可以得到以下幾個結論: 
結論一,memcached得LRU不是全局的,而是針對slab的,可以說是區域性的。 
結論二,要提高memcached的命中率,預估我們的value大小並且適當的調整內存頁大小和增長因子是必須的。 
結論三,帶着問題找答案理解的要比隨便看看的效果好得多。

轉至:http://www.javaeye.com/topic/225692

來自: http://hi.baidu.com/xkplt/blog/item/e3813a2ad2c62d345243c113.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章