一、Memcache內存分配機制
關於這個機制網上有很多解釋的,我個人的總結如下。
Page為內存分配的最小單位。
Memcached 的內存分配以page為單位,默認情況下一個page是1M,可以通過-I參數在啟動時指定。如果需要申請內存 時,memcached會划分出一個新的page並分配給需要的slab區域。page一旦被分配在重啟前不會被回收或者重新分配(page ressign已經從1.2.8版移除了)
Slabs划分數據空間。
Memcached 並不是將所有大小的數據都放在一起的,而是預先將數據空間划分為一系列slabs,每個slab只負責一定范圍內的數據存儲。如 下圖,每個slab只存儲大於其上一個slab的size並小於或者等於自己最大size的數據。例如:slab 3只存儲大小介於137 到 224 bytes的數據。如果一個數據大小為230byte將被分配到slab 4中。從下圖可以看出,每個slab負責的空間其實是不等的,memcached默認情況下下一個slab的最大值為前一個的1.25倍,這個可以通過修 改-f參數來修改增長比例。
Chunk才是存放緩存數據的單位。
Chunk 是一系列固定的內存空間,這個大小就是管理它的slab的最大存放大小。例如:slab 1的所有chunk都是104byte,而slab 4的所有chunk都是280byte。chunk是memcached實際存放緩存數據的地方,因為chunk的大小固定為slab能夠存放的最大值, 所以所有分配給當前slab的數據都可以被chunk存下。如果時間的數據大小小於chunk的大小,空余的空間將會被閑置,這個是為了防止內存碎片而設 計的。例如下圖,chunk size是224byte,而存儲的數據只有200byte,剩下的24byte將被閑置。
Slab的內存分配。
Memcached在啟動時通過-m指定最大使用內存,但是這個不會一啟動就占用,是隨着需要逐步分配給各slab的。
如果一個新的緩存數據要被存放,memcached首先選擇一個合適的slab,然后查看該slab是否還有空閑的chunk,如果有則直接存放進去;如 果沒有則要進行申請。slab申請內存時以page為單位,所以在放入第一個數據,無論大小為多少,都會有1M大小的page被分配給該slab。申請到 page后,slab會將這個page的內存按chunk的大小進行切分,這樣就變成了一個chunk的數組,在從這個chunk數組中選擇一個用於存儲 數據。如下圖,slab 1和slab 2都分配了一個page,並按各自的大小切分成chunk數組。
Memcached內存分配策略。
綜合上面的介紹,memcached的內存分配策略就是:按slab需求分配page,各slab按需使用chunk存儲。
這里有幾個特點要注意,
Memcached分配出去的page不會被回收或者重新分配Memcached申請的內存不會被釋放slab空閑的chunk不會借給任何其他slab使用
知道了這些以后,就可以理解為什么總內存沒有被全部占用的情況下,memcached卻出現了丟失緩存數據的問題了。


http://blog.csdn.net/lcli2009/article/details/22095251
http://blog.csdn.net/lcli2009/article/details/21985793
- typedef struct _stritem {
- struct _stritem *next;//item在slab中存儲時,是以雙鏈表的形式存儲的,next即后向指針
- struct _stritem *prev;//prev為前向指針
- struct _stritem *h_next;//Hash桶中元素的鏈接指針
- rel_time_t time; //最近訪問時間
- rel_time_t exptime;//過期時間
- int nbytes;//數據大小
- unsigned short refcount;//引用次數
- uint8_t nsuffix;
- uint8_t it_flags;
- uint8_t slabs_clsid;//標記item屬於哪個slabclass下
- uint8_t nkey; //key的長度
- union {
- uint64_t cas;
- char end;
- } data[];//真實的數據信息
- } item;


If the new item is bigger than the size of any existing blocks, then a new slab is created, divided up into blocks of a suitable size. If an existing slab with the right block size already exists, but there are no free blocks, a new slab is created.
內存頁一旦被分配給slab,在memcache生命周期內不再更改,在內存總量確定的情形下,其它slab可能出現餓死現象;
unsigned int size; /* 每個item大小, sizes of items */
unsigned int perslab; /* 每個page中包含多少個item , how many items per slab */
void **slots; /* 空閑的item指針, list of item ptrs */
unsigned int sl_total; /* 以分配空閑的item 個數, size of previous array */
unsigned int sl_curr; /* 當前空閑的item位置(也就是實際空閑item個數),從后往前的, first free slot */
void *end_page_ptr; /* 指向最后一個頁面中空閑的item開始位置, pointer to next free item at end of page, or 0 */
unsigned int end_page_free; /* 最后一個頁面,item個數, number of items remaining at end of last alloced page */
unsigned int slabs; /* 實際使用slab(page)個數 how many slabs were allocated for this class */
void **slab_list; /* 所有page的指針, array of slab pointers */
unsigned int list_size; /* 已經分配page指針個數,size of prev array */
unsigned int killing; /* index+1 of dying slab, or zero if none */
size_t requested; /* 所有被使用了的內存的大小, The number of requested bytes */
} slabclass_t;


do_item_remove(it);//刪除item
do_item_remove
slabs_free(it, ntotal, clsid);//slabclass結構執行釋放
it->it_flags |= ITEM_SLABBED;//修改item的狀態標識,修改為空閑
it->prev = 0;//斷開數據鏈表
it->next = p->slots;
if (it->next) it->next->prev = it;
p->slots = it;
int tries = 5;//如果LRU中嘗試5次還沒合適的空間,則執行申請空間的操作
search = tails[id];//第id個LRU表的尾部
it = search;
......
}
it = slabs_alloc(ntotal, id);//則從內存池申請新的空間
}
// 1 最后面的頁面有空間 2 空閑的地方有空間 3 分配一個新的頁面
if (! (p->end_page_ptr != 0 || p->sl_curr != 0 || do_slabs_newslab(id) != 0)) {
/* We don't have more memory available */
ret = NULL;
} else if (p->sl_curr != 0) {
/* return off our freelist */
//從空閑(回收)地方分配
ret = p->slots[--p->sl_curr];
} else {
/* if we recently allocated a whole page, return from that */
assert(p->end_page_ptr != NULL);
ret = p->end_page_ptr;
if (--p->end_page_free != 0) {
p->end_page_ptr = ((caddr_t)p->end_page_ptr) + p->size;
} else {
p->end_page_ptr = 0;
}
}
if (ret) {
p->requested += size;
MEMCACHED_SLABS_ALLOCATE(size, id, p->size, ret);
} else {
MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);
}
return ret;
}