mdb存储引擎实现

存储引擎整体结构

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;

猜你喜欢

转载自blog.csdn.net/shangshengshi/article/details/78785013