天天看點

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