
日期:2008-07-17 作者:喜騰小二 來源:PHPChina
版權宣告:可以任意轉載,但轉載時必須標明原作者charlee、原始連結http://tech.idv2.com/2008/07/11/memcached-002/以及本宣告。
下麵是《memcached全麵剖析》的第二部分。
發表日:2008/7/9
作者:前坂徹(Toru Maesaka)
原文連結:http://gihyo.jp/dev/feature/01/memcached/0002
我是mixi株式會社研究開發組的前坂徹。 上次的文章介紹了memcached是分散式的高速快取伺服器。本次將介紹memcached的內部構造的實現方式,以及記憶體的管理方式。另外,memcached的內部構造導緻的弱點也將加以幫助。
最近的memcached預設情況下采用了名為Slab Allocator的機制分配、管理記憶體。在該機制出現以前,記憶體的分配是透過對所有記錄簡單地進行malloc和free來進行的。但是,這種方式會導緻記憶體碎片,加重操作係統記憶體管理員的負擔,最壞的情況下,會導緻操作係統比memcached處理序本身還慢。Slab Allocator就是為解決該問題而誕生的。
下麵來看看Slab Allocator的原理。下麵是memcached文檔中的slab allocator的目的:
the primary goal of the slabs subsystem in memcached was to eliminate memory fragmentation issues totally by using fixed-size memory chunks coming from a few predetermined size classes.
也就是說,Slab Allocator的基本原理是按照預先規定的大小,將分配的記憶體分割成特定長度的塊,以完全解決記憶體碎片問題。
Slab Allocation的原理相當簡單。 將分配的記憶體分割成各種尺寸的塊(chunk),並把尺寸相同的塊分成組(chunk的集合)(圖1)。

圖1 Slab Allocation的構造圖
而且,slab allocator還有重複使用已分配的記憶體的目的。也就是說,分配到的記憶體不會釋放,而是重複利用。
Page
分配給Slab的記憶體空間,預設是1MB。分配給Slab之後根據slab的大小切分成chunk。
Chunk
用於快取記錄的記憶體空間。
Slab Class
特定大小的chunk的組。
下麵幫助memcached如何針對用戶端發送的資料選擇slab並快取到chunk中。
memcached根據收到的資料的大小,選擇最適合資料大小的slab(圖2)。 memcached中儲存着slab內空閒chunk的清單,根據該清單選擇chunk,然後將資料快取於其中。

圖2 選擇存儲記錄的組的方法
實際上,Slab Allocator也是有利也有弊。下麵介紹一下它的缺點。
Slab Allocator解決了當初的記憶體碎片問題,但新的機制也給memcached帶來了新的問題。
這個問題就是,由於分配的是特定長度的記憶體,因此無法有效利用分配的記憶體。例如,將100位元組的資料快取到128位元組的chunk中,剩餘的28位元組就浪費了(圖3)。

圖3 chunk空間的使用
對於該問題目前還沒有完美的解決方案,但在文檔中記載了比較有效的解決方案。
The most efficient way to reduce the waste is to use a list of size classes that closely matches (if that's at all possible) common sizes of objects that the clients of this particular installation of memcached are likely to store.
就是說,如果預先知道用戶端發送的資料的公用大小,或者僅快取大小相同的資料的情況下,只要使用適合資料大小的組的清單,就可以減少浪費。
但是很遺憾,現在還不能進行任何調優,隻能期待以後的版本了。但是,我們可以調節slab class的大小的差別。接下來幫助growth factor選項。
memcached在啓動時指定 Growth Factor因數(透過-f選項),就可以在某種程度上控制slab之間的差異。預設值為1.25。但是,在該選項出現之前,這個因數曾經固定為2,稱為“powers of 2”策略。
讓我們用以前的設定,以verbose模式啓動memcached試試看:
$ memcached -f 2 -vv下麵是啓動後的verbose輸出:
slab class 1: chunk size 128 perslab 8192可見,從128位元組的組開始,組的大小依次增大為原來的2倍。這樣設定的問題是,slab之間的差別比較大,有些情況下就相當浪費記憶體。因此,為盡量減少記憶體浪費,兩年前追加了growth factor這個選項。
來看看現在的預設設定(f=1.25)時的輸出(篇幅所限,這裡唯寫到第10組):
slab class 1: chunk size 88 perslab 11915可見,組間差距比因數為2時小得多,更適合快取幾百位元組的記錄。從上麵的輸出結果來看,可能會覺得有些計算誤差,這些誤差是為了保持位元組數的對齊而故意設定的。
將memcached引入產品,或是直接使用預設值進行部署時,最好是重新計算一下資料的預期平均長度,調整growth factor,以獲得最恰當的設定。記憶體是珍貴的資源,浪費就太可惜了。
接下來介紹一下如何使用memcached的stats指令檢視slabs的利用率等各種各樣的資訊。
memcached有個名為stats的指令,使用它可以獲得各種各樣的資訊。執行指令的方法很多,用telnet最為簡單:
$ telnet 主機名 連接埠號連線到memcached之後,輸入stats再按回車,即可獲得包括資源利用率在內的各種資訊。此外,輸入"stats slabs"或"stats items"還可以獲得關於快取記錄的資訊。結束程式請輸入quit。
這些指令的詳細資訊可以參考memcached軟體包內的protocol.txt文檔。
$ telnet localhost 11211另外,如果安裝了libmemcached這個麵嚮C/C++語言的用戶端庫,就會安裝 memstat 這個惡指令。使用方法很簡單,可以用更少的步驟獲得與telnet相同的資訊,還能一次性從多臺伺服器獲得資訊。
$ memstat --servers=server1,server2,server3,...libmemcached可以從下麵的地址獲得:
使用memcached的創造着Brad寫的名為memcached-tool的Perl指令檔,可以方便地獲得slab的使用情況(它將memcached的返回值整理成容易閱讀的格式)。可以從下麵的地址獲得指令檔:
使用方法也極其簡單:
$ memcached-tool 主機名:連接埠 選項檢視slabs使用狀況時無需指定選項,因此用下麵的指令即可:
$ memcached-tool 主機名:連接埠獲得的資訊如下所示:
# Item_Size Max_age 1MB_pages Count Full?各列的含義為:
| 列 | 含義 |
| # | slab class編號 |
| Item_Size | Chunk大小 |
| Max_age | LRU內最舊的記錄的生存時間 |
| 1MB_pages | 分配給Slab的頁數 |
| Count | Slab內的記錄數 |
| Full? | Slab內是否含有空閒chunk |
從這個指令檔獲得的資訊對於調優非常方便,強烈推薦使用。
本次簡單幫助了memcached的快取機制和調優方法。希望讀者能理解memcached的記憶體管理原理及其優缺點。
下次將繼續幫助LRU和Expire等原理,以及memcached的最新發展方嚮—— 可擴充體係(pluggable architecher))。