Memcached原始碼分析之儲存機制Slabs(7)
文章列表:
《Memcached原始碼分析 - Memcached原始碼分析之總結篇(8)》前言
這一章我們重點講解Memcached的儲存機制Slabs。Memcached儲存Item的程式碼都是在slabs.c中來實現的。
在解讀這一章前,我們必須先了解幾個概念。
Item 快取資料儲存的基本單元
1. Item是Memcached儲存的最小單位
2. 每一個快取都會有自己的一個Item資料結構
3. Item主要儲存快取的key、value、key的長度、value的長度、快取的時間等資訊。
4. HashTable和LRU連結串列結構都是依賴Item結構中的元素的。
5. 在Memcached中,Item扮演著重要的角色。
//item的具體結構 typedef struct _stritem { //記錄下一個item的地址,主要用於LRU鏈和freelist鏈 struct _stritem *next; //記錄下一個item的地址,主要用於LRU鏈和freelist鏈 struct _stritem *prev; //記錄HashTable的下一個Item的地址 struct _stritem *h_next; //最近訪問的時間,只有set/add/replace等操作才會更新這個欄位 //當執行flush命令的時候,需要用這個時間和執行flush命令的時間相比較,來判斷是否失效 rel_time_t time; /* least recent access */ //快取的過期時間。設定為0的時候,則永久有效。 //如果Memcached不能分配新的item的時候,設定為0的item也有可能被LRU淘汰 rel_time_t exptime; /* expire time */ //value資料大小 int nbytes; /* size of data */ //引用的次數。通過這個引用的次數,可以判斷item是否被其它的執行緒在操作中。 //也可以通過refcount來判斷當前的item是否可以被刪除,只有refcount -1 = 0的時候才能被刪除 unsigned short refcount; uint8_t nsuffix; /* length of flags-and-length string */ uint8_t it_flags; /* ITEM_* above */ //slabs_class的ID。 uint8_t slabs_clsid;/* which slab class we're in */ uint8_t nkey; /* key length, w/terminating null and padding */ /* this odd type prevents type-punning issues when we do * the little shuffle to save space when not using CAS. */ //資料儲存結構 union { uint64_t cas; char end; } data[]; /* if it_flags & ITEM_CAS we have 8 bytes CAS */ /* then null-terminated key */ /* then " flags length\r\n" (no terminating null) */ /* then data with terminating \r\n (no terminating null; it's binary!) */ } item;
slabclass 劃分資料空間
1. Memcached在啟動的時候,會初始化一個slabclass陣列,該陣列用於儲存最大200個slabclass_t的資料結構體。
2. Memcached並不會將所有大小的資料都會放置在一起,而是預先將資料空間劃分為一系列的slabclass_t。
3. 每個slabclass_t,都只儲存一定大小範圍的資料。slabclass陣列中,前一個slabclass_t可以儲存的資料大小要小於下一個slabclass_t結構可以儲存的資料大小。
4. 例如:slabclass[3]只儲存大小介於120 (slabclass[2]的最大值)到 150 bytes的資料。如果一個數據大小為134byte將被分配到slabclass[3]中。
5. memcached預設情況下下一個slabclass_t儲存資料的最大值為前一個的1.25倍(settings.factor),這個可以通過修 改-f引數來修改增長比例。
//slabclass的結構
typedef struct {
//當前的slabclass儲存最大多大的item
unsigned int size;
//每一個slab上可以儲存多少個item.每個slab大小為1M, 可以儲存的item個數根據size決定。
unsigned int perslab;
//當前slabclass的(空閒item列表)freelist 的連結串列頭部地址
//freelist的連結串列是通過item結構中的item->next和item->prev連建立連結串列結構關係
void *slots; /* list of item ptrs */
//當前總共剩餘多少個空閒的item
//當sl_curr=0的時候,說明已經沒有空閒的item,需要分配一個新的slab(每個1M,可以切割成N多個Item結構)
unsigned int sl_curr; /* total free items in list */
//總共分配多少個slabs
unsigned int slabs; /* how many slabs were allocated for this class */
//分配的slab連結串列
void **slab_list; /* array of slab pointers */
unsigned int list_size; /* size of prev array */
unsigned int killing; /* index+1 of dying slab, or zero if none */
//總共請求的總bytes
size_t requested; /* The number of requested bytes */
} slabclass_t;
//定義一個slabclass陣列,用於儲存最大200個的slabclass_t的結構。
static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES];
slab 記憶體分配單位
1. Memcached的記憶體分配是以slab為單位的。預設情況下,每個slab大小為1M。
2. slabclass陣列初始化的時候,每個slabclass_t都會分配一個1M大小的slab。
3. 當某個slabclass_t結構上的記憶體不夠的時候(freelist空閒列表為空),則會分配一個slab給這個slabclass_t結構。
4. 一旦slab分配後,不可回收。
5. slab會被切分為N個小的記憶體塊,這個小的記憶體塊的大小取決於slabclass_t結構上的size的大小。例如slabclass[0]上的size為103,則每個小的記憶體塊大小為103byte。
6. 這些被切割的小的記憶體塊,主要用來儲存item。但是,儲存的item,可能會比切割出來的記憶體塊會小。因為這是為了防止記憶體碎片,雖然有一些記憶體的浪費。
slabclass和slab、item以及free list之間的關係:
通過item的size來選擇slab_class的資料儲存空間:
儲存機制的原始碼分析
slabs_clsid - 查詢slabclass的ID
slabs_clsid方法,主要通過item的長度來查詢應該適合存放到哪個slabsclass_t上面。
//通過item的size,選擇當前的item適合放在哪個slab class中
unsigned int slabs_clsid(const size_t size) {
int res = POWER_SMALLEST; //從id = 1開始查詢
//slabclass這個結構上的size會儲存該class適合多大的item儲存
//例如
//slabclass[0] 儲存96byte
//slabclass[1] 儲存120byte
//slabclass[2] 儲存150byte
//則,如果儲存的item等於109byte,則儲存在slabclass[1]上
if (size == 0)
return 0;
while (size > slabclass[res].size)
if (res++ == power_largest) /* won't fit in the biggest slab */
return 0;
return res;
}
slabs_init - slabclass的初始化
slabs_init方法主要用於初始化slabclass陣列結構。
//slabclass初始化
void slabs_init(const size_t limit, const double factor, const bool prealloc) {
int i = POWER_SMALLEST - 1;
unsigned int size = sizeof(item) + settings.chunk_size;
mem_limit = limit;
//這邊是否初始化的時候,就給每一個slabclass_t結構分配一個slab記憶體塊
//預設都會分配
if (prealloc) {
/* Allocate everything in a big chunk with malloc */
mem_base = malloc(mem_limit);
if (mem_base != NULL) {
mem_current = mem_base;
mem_avail = mem_limit;
} else {
fprintf(stderr, "Warning: Failed to allocate requested memory in"
" one large chunk.\nWill allocate in smaller chunks\n");
}
}
memset(slabclass, 0, sizeof(slabclass));
//factor 預設等於1.25 ,也就是說前一個slabclass允許儲存96byte大小的資料,
//則下一個slabclass可以儲存120byte
while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {
/* Make sure items are always n-byte aligned */
if (size % CHUNK_ALIGN_BYTES)
size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
//每個slabclass[i]儲存最大多大的item
slabclass[i].size = size;
slabclass[i].perslab = settings.item_size_max / slabclass[i].size;
size *= factor;
if (settings.verbose > 1) {
fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
i, slabclass[i].size, slabclass[i].perslab);
}
}
power_largest = i;
slabclass[power_largest].size = settings.item_size_max;
slabclass[power_largest].perslab = 1;
if (settings.verbose > 1) {
fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
i, slabclass[i].size, slabclass[i].perslab);
}
/* for the test suite: faking of how much we've already malloc'd */
{
char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
if (t_initial_malloc) {
mem_malloced = (size_t)atol(t_initial_malloc);
}
}
if (prealloc) {
slabs_preallocate(power_largest);
}
}
//分配記憶體
static void slabs_preallocate (const unsigned int maxslabs) {
int i;
unsigned int prealloc = 0;
/* pre-allocate a 1MB slab in every size class so people don't get
confused by non-intuitive "SERVER_ERROR out of memory"
messages. this is the most common question on the mailing
list. if you really don't want this, you can rebuild without
these three lines. */
//給每一個slabclass_t結構分配一個預設的slab
for (i = POWER_SMALLEST; i <= POWER_LARGEST; i++) {
if (++prealloc > maxslabs)
return;
if (do_slabs_newslab(i) == 0) {
fprintf(stderr, "Error while preallocating slab memory!\n"
"If using -L or other prealloc options, max memory must be "
"at least %d megabytes.\n", power_largest);
exit(1);
}
}
}
do_slabs_alloc - 分配一個item
1. Memcached分配一個item,會先檢查freelist空閒的列表中是否有空閒的item,如果有的話就用空閒列表中的item。
2. 如果空閒列表沒有空閒的item可以分配,則Memcached會去申請一個slab(預設大小為1M)的記憶體塊,如果申請失敗,則返回NULL,表明分配失敗。
3. 如果申請成功,則會去將這個1M大小的記憶體塊,根據slabclass_t可以儲存的最大的item的size,將slab切割成N個item,然後放進freelist(空閒列表中)
4. 然後去freelist(空閒列表)中取出一個item來使用。
//分配一個Item
void *slabs_alloc(size_t size, unsigned int id) {
void *ret;
//分配Item前需要上執行緒鎖
pthread_mutex_lock(&slabs_lock);
//size:需要分配的item的長度
//id:需要分配在哪個slab class上面
ret = do_slabs_alloc(size, id);
pthread_mutex_unlock(&slabs_lock);
return ret;
}
//分配一個Item
static void *do_slabs_alloc(const size_t size, unsigned int id) {
slabclass_t *p;
void *ret = NULL;
item *it = NULL;
if (id < POWER_SMALLEST || id > power_largest) {
MEMCACHED_SLABS_ALLOCATE_FAILED(size, 0);
return NULL;
}
//獲取slabclass
p = &slabclass[id];
assert(p->sl_curr == 0 || ((item *)p->slots)->slabs_clsid == 0);
/* fail unless we have space at the end of a recently allocated page,
we have something on our freelist, or we could allocate a new page */
//p->sl_curr 說明是否有空閒的item list
//如果沒有空閒的item list,則取分配一個新的slab,如果分配失敗,返回NULL
if (! (p->sl_curr != 0 || do_slabs_newslab(id) != 0)) {
/* We don't have more memory available */
ret = NULL;
//如果有free item lits,則從空閒的列表中取一個Item
} else if (p->sl_curr != 0) {
/* return off our freelist */
it = (item *)p->slots;
p->slots = it->next;
if (it->next) it->next->prev = 0;
p->sl_curr--;
ret = (void *)it;
}
if (ret) {
p->requested += size;
MEMCACHED_SLABS_ALLOCATE(size, id, p->size, ret);
} else {
MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);
}
return ret;
}
分配一個新的slab:
//分配一塊新的item塊
static int do_slabs_newslab(const unsigned int id) {
//獲取slabclass
slabclass_t *p = &slabclass[id];
//分配一個slab,預設是1M
//分配的slab也可以根據 該slabclass儲存的item的大小 * 可以儲存的item的個數 來計算出記憶體塊長度
int len = settings.slab_reassign ? settings.item_size_max
: p->size * p->perslab;
char *ptr;
//這邊回去分配一塊slab記憶體塊
if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0) ||
(grow_slab_list(id) == 0) ||
((ptr = memory_allocate((size_t)len)) == 0)) {
MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
return 0;
}
//將slab記憶體記憶體塊切割成N個item,放進freelist中
memset(ptr, 0, (size_t)len);
split_slab_page_into_freelist(ptr, id);
p->slab_list[p->slabs++] = ptr;
mem_malloced += len;
MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id);
return 1;
}
將slab記憶體塊進行切割:
//將slab 切割成N個item
static void split_slab_page_into_freelist(char *ptr, const unsigned int id) {
slabclass_t *p = &slabclass[id];
int x;
for (x = 0; x < p->perslab; x++) {
//將指標傳遞給ptr,free操作並不是真正意義上的釋放記憶體塊,只是將記憶體塊放到free list(空閒列表上面)
do_slabs_free(ptr, 0, id);
//這邊使用p->size,來設定每個item記憶體塊的大小
//實際儲存的時候,item的size都會小於p->size
ptr += p->size;
}
}
slabs_free - 釋放一個item
釋放item後,會將item放進free list(空閒列表中)。
//釋放一個Item
void slabs_free(void *ptr, size_t size, unsigned int id) {
pthread_mutex_lock(&slabs_lock);
//ptr:item的指標
//size:item的大小
//id:在哪個slab class上面
do_slabs_free(ptr, size, id);
pthread_mutex_unlock(&slabs_lock);
}
//釋放一個item
static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {
slabclass_t *p;
item *it;
assert(((item *)ptr)->slabs_clsid == 0);
assert(id >= POWER_SMALLEST && id <= power_largest);
if (id < POWER_SMALLEST || id > power_largest)
return;
MEMCACHED_SLABS_FREE(size, id, ptr);
p = &slabclass[id];
it = (item *)ptr;
it->it_flags |= ITEM_SLABBED;
//放進空閒列表 freelist
it->prev = 0;
it->next = p->slots;
if (it->next) it->next->prev = it;
p->slots = it;
p->sl_curr++;
p->requested -= size;
return;
}