Memcache的原理的詳解

轉載地址:http://sameveryday.blog.163.com/blog/static/178072337201411095357740/ java


Memcache工做原理
linux

首 先 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) 的值在哪臺服務器上就能夠了。
c++

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

memcache 命中率緩存

首先查看,服務器

在linux或window上有telnet鏈接memcache,而後輸入stats會出現下面的命令,這些狀態的說明以下:異步

pid
memcache服務器的進程ID
ide

uptime
服務器已經運行的秒數
memcached

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
當前線程數

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

memcached/scripts/memcached-too

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大小而且適當的調整內存頁大小和增加因子是必須的。 結論三,帶着問題找答案理解的要比隨便看看的效果好得多。

相關文章
相關標籤/搜索