memcached server LRU 深入分析

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,回車,出現如下數據:
Java代碼收藏代碼
  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的一些健康情況,比如說命中率之類的,示例如下:
Java代碼收藏代碼
  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大小並且適當的調整內存頁大小和增長因子是必須的。
結論三,帶着問題找答案理解的要比隨便看看的效果好得多。



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