存储引擎整体结构
Memory pool
如图所示,mdb存储引擎针对一块大的内存进行操作,内存引擎实现的原理如下:
初始化
- 首先分配一块大的内存,然后初始mem_pool对象,为mem_pool_impl对象中相关page的元数据赋值,如page_size、total_pages、meta_pages、free_pages、current_page等。
- 初始化mem_cache对象,初始化cache_info元数据对象,初始化cache_info中max_slab_id、base_size、factor相关成员。
- 初始化mem_cache对象中slab_manager对象数组,slab_manager数组大小为cache_info对象中max_slab_id,每个slab_manager还包括一个partial_pages的数组,数组的大小为该slab管理的page中item数决定,计算方式为: ( item数+(10-1)/10 )。
内存分配
- 分配指定大小的内存时,会为其在slab_manager数组中选择一个合适的slab(管理的长度大于指定的内存大小的最小的slab)。
for (size_t i = 0; i < slab_managers.size(); ++i)
{
if (slab_managers[i]->slab_size >= size)
{
mgr = slab_managers[i];
break;
}
}
通过获取的slab分配内存,内存分配的顺序为首先判断是否有partial_pages_no,如果有则直接从部分可用的page中分配item,如果没有部分可用的page,则直接判断是否有全空闲的page,如果有,则从全空闲的page中分配item,如果上述两种情况都没有,则从mem_pool中分配并初始化一块空闲的page,分配item,并且将该page连接到该slab对应的部分可用的page中。
说明:
在通过slab成功分配了item后,会将item连接到hashtable的链表中,具体的连接方式我们在后面的hashtable一节中详细描述,通过hashtable管理分配后的item的好处是提高查找的效率,减少相应操作的时间。删除记录操作时,实际是调用存储引擎的删除item的接口,同分配内存一样,也是根据item的大小,确定对应管理该内存的slab,删除的主要操作包括:
1)将该item从hashtable的链表中删除。
2)获取该item所在页,根据该页当前的状态是全满还是部分满,更新释放该item后的page状态,对于部分空闲的page,还需要根据空闲的item数重新将该page连接到对应的部分空闲队列上。
Hashtable:
这里主要介绍hashtable对应链表中的插入及删除操作,hashtable中每一个bucket的类型为uint64_t数据类型,对应item的id。
item初始化:
for(int i = 0; i < per_slab; ++i)
{
item->next = item->prev = 0;
item->item_id = ITEM_ID(slab_size, i, index, slab_id);
item->h_next = ITEM_ID(slab_size, i + 1, index, slab_id);
item = reinterpret_cast<mdb_item *>((char*)item + slab_siz);
}
item相关主要操作:
#define ITEM_KEY(it) (&((it)->data[0]))
#define ITEM_DATA(it) (&((it)->data[0]) + (it)->key_len)
#define ITEM_AREA(it) (((it)->data[0]&0xff)|(((it)->data[1]<<8)&0xff00))
#define KEY_AREA(key) ((key[0]&0xff)|((key[1]<<8)&0xff00))
#define ITEM_ID(_slab_size,_offset,_page_id,_slab_id) \
({ \
(((uint64_t)_slab_id) << 52 \
|((uint64_t)_page_id)<<36 \
|((uint64_t)_offset) << 20 \
|((uint64_t)_slab_size));})
#define ITEM_ADDR(base,item_id,page_size) \
({assert(item_id != 0); \
reinterpret_cast<mdb_item *>(base \
+ PAGE_ID(item_id) * page_size \
+ SLAB_SIZE(item_id) * PAGE_OFFSET(item_id)
+ sizeof(mem_cache::page_info));})
#define id_to_item(id) \
ITEM_ADDR(this_mem_pool->get_pool_addr(),id,mdb_param::page_size )
item插入hashtable:
插入时,先将item映射到某一个bucket上,然后将该bucket上的itemid赋值给item->next,然后将item的itemid赋值给该bucket的第一过程就是一个链表第一个元素的入过程,时间复杂度为o(1)。
int idx = get_bucket_index(item);
item->h_next = hashtable[idx];
hashtable[idx] = item->item_id;
hashtable中删除item:
删除时,先通过find函数遍历链表,根据遍历查找的结果,判断要删除的节点是否存在、是链首还是非链首位置,根据对应的结果从链表中删除item节点。
int idx = get_bucket_index(item);
uint64_t pos = hashtable[idx];
mdb_item *prev = 0;
mdb_item *pprev = 0;
prev = __find(pos, ITEM_KEY(item), item->key_len, &pprev);
if(prev == 0) // not found
{
return false;
}
if(pprev)
{
pprev->h_next = prev->h_next;
}
else
{
hashtable[idx] = prev->h_next;
}
prev->h_next = 0;