memcached的一些研究(關於memcached的記憶體分配機制)
memcached作為快取已被應用的非常多,memcached的資料結構非常簡單,就是key-value的儲存,瞭解下memcached的記憶體分配機制有助於更好的使用memcache
memcached相關的記憶體術語
- chunk:資料是儲存在稱為chunk的記憶體空間裡的
- page:已有快取空間滿了以後,每次會申請一個page(預設一個page為1M),page包含成多個記憶體大小相等的chunk
- slab class:slab有很多相同的chunk,slab是根據具體的快取情況決定大小的;每次申請的page都在slab下;
- 具體的模型類似下圖:
記憶體分配機制
memcached的記憶體分配機制決定了memcached的優缺點,比如不適合儲存比較大的資料,對小資料儲存非常快
memcached記憶體採用slab allocator機制
那什麼是slab allocator?(以啟動配置為:增長因子為2,page大小為1M)
- memcached能管理的記憶體通過引數 -m 100m來設定(這裡指定了100M)
- memcached根據page大小,來對slab進行分配,在page大小為1M,增長因子為2的情況下,分配slab為:第一個slab的chunk大小為96B(預設以96B開始),第二個slab的chunk大小為192B(96B*2),以此類推,一直到1M大小(你會發現最後一個跟最後第二個的大小不是呈現
*2
的規律,這個在後文再解釋),所有slab的個數是由page大小和增長因子決定的 執行
memcached -f 2 -vv
的結果:slab class 1: chunk size 96 perslab 10922 slab class 2: chunk size 192 perslab 5461 slab class 3: chunk size 384 perslab 2730 slab class 4: chunk size 768 perslab 1365 slab class 5: chunk size 1536 perslab 682 slab class
一共分配了14個slab class,最後一個和倒數第二個並不呈現增長因子的規律
可以看到在啟動的時候,這14個slab class都分配到最接近1M大小的記憶體,這裡並不是完整的1M,只有slab class 14是1M;
- 那資料如何選擇儲存在哪個slab class下的chunk中呢?答案是能儲存下需要儲存資料大小的最小的slab class的chunk中;舉個栗子:有slab class 1(96B),slab class 2(192B),slab class 3(384B),….;那麼94B的資料就儲存在slab class 1,98B的資料需要儲存在slab class 2中,換句話說就是:slab class 1儲存0~96B的資料,slab class 2儲存97B~192B的資料,slab class 3儲存193B~384B的資料,以此類推;這樣就會發現,極端情況下會有將近50%的儲存空間浪費(slab class 1都儲存小於1B的資料,slab class 2都儲存97B的資料,……);但真實情況不會有這麼多,但肯定是會有空間浪費的,因為不會出現另一個極端(slab class 1都儲存小於96B的資料,slab class 2都儲存192B的資料,……);所以這裡就是一個調優的點了,需要根據實際應用中需要快取的資料大小分佈來進行增長因子的調整;(合理設定增長因子能夠減少記憶體的浪費)
當slab class中的chunk分配完了,那怎麼辦?這裡分2中情況
- memcached管理的記憶體還有超過1個page的大小可以分配,那麼就分配一個page給slab class,繼續儲存資料
- 如果沒有足夠記憶體繼續分配了,預設情況下采用LRU(latest recent use)演算法進行快取失效;也可以配置成FIFO(first in first out)演算法;也可以配置成不進行快取失效,這樣就儲存不了資料了,memcached返回儲存失敗
memcached在set的時候需要設定快取失效時間,但快取時間到了,memcached並不會把資料給抹掉,只是在get的時候去檢測快取是否失效,這種失效機制成為lazy expire,這種做法去掉了系統監視快取失效的開銷,大大提高了效能
一些細節問題
為什麼memcached分配記憶體的時候,需要用多少分配多少呢?
如果這樣做,那就是實時進行記憶體分配了,那麼很多的開銷都花在了記憶體的分配及回收上了,效能就會大打折扣;預先分配可以減免這方面的開銷;其實這就是典型的以空間換取時間的做法
經常有人問,為什麼我往memcached儲存超過1M
因為memcached預設的page大小剛好為1M,而chunk是由page(即1M大小分拆成n個chunk,最大的chunk也就是1M),而資料儲存在chunk中,當然儲存不了超過1M的資料;如果非得儲存超過1M的資料,那麼只能去修改page的大小了,具體怎麼修改,網上應該有很多;memcached非常適合儲存資料比較小的快取,當資料比較大的時候,如儲存的資料都解決page的大小的情況下,會進行頻繁的記憶體分配操作,效能就會降下來
上面提到的一個問題:為什麼最後一個slab class的chunk大小為1M,而最後第二個slab不是512K呢?
memcached為了保證能儲存的最大的資料剛好為一個page(這裡是1M)的大小,在分配演算法上做了處理,就是當倒數第二個slab class的chunk大小乘以增長因子不到1M,而倒數第二個slab class的chunk大小乘以增長因子再乘以增長因子超過1M,那麼直接就分配最後一個為1M的slab;
細心的同學可能會發現,增長因子設定以後,slab class的chunk記憶體空間增長並不是按照前一個slab class的chunk乘以增長因子的
對於這個問題沒有去研究;在這裡根據實驗表現來推測:
如果設定增長因子為1.0104,總共有63個slab class,且最後一個slab class的chunk大小為1M,其他均為96B;
如果增長因子設定為1.0105,也是有63個slab class,最後一個為slab class的chunk大小為1M,其他為遞增的方式,第一個為96B,第二個未104B,第三個為112B;
如果增長因子設定為1.084,也是有63個slab class,最後一個為slab class的chunk大小為1M,其他為遞增的方式,第一個為96B,第二個未104B,第三個為112B;
如果增長因子設定為1.1,也是有63個slab class,最後一個為slab class的chunk大小為1M,其他為遞增的方式,第一個為96B,第二個未112B,第三個為112B;
發現規律過程:96B乘以1.0104=96.9984;96B乘以1.0105=97.008;96B乘以1.084=104.064;96B乘以1.1=105.6;可以看出memcached最多有63個slab class;另外推測,如果當前chunk大小乘以增長因子的整數部分跟當前chunk相等,那麼將當前slab class的chunk大小設定為下一個slab class的chunk大小,直到最後一個slab class(第63個),這種情況也就是在第一個slab class的chunk大小乘以增長因子小於97B的時候會發生;再看增長因子為1.0105的時候,第二個slab class的chunk大小為104,增長因子為1.084的時候,第二個slab class的chunk大小為104,增長因子為1.1的時候,第二個slab class的chunk大小為112,這裡可以發現另外一個規律:增長每次都是按照至少8B的跨度進行增長的;
具體為什麼這樣設計,我也不清楚;上面的結論也是靠猜的,具體的還是需要檢視memcached的原始碼,看看到底是如何分配記憶體的;
其他
- 關於memcached如何儲存key的,為什麼效能能如此之高,號稱set和get操作的時間複雜度都是O(1),這個非常值得研究,後續會繼續研究memcached的set、get等操作為何會如此之快,等研究完再進行分享