memcached的内存管理机制

来源:互联网 发布:苹果手机不能用4g网络 编辑:程序博客网 时间:2024/06/05 03:22

memcached内存的管理

1.1重要概念 slab和chunk

如下图所示slab是一个人内存块,memcached 一次性申请内存的最小单位,在启动memecached可用-m指定其可用内存的大小,但是不是在启动memcaced就把内存分配出去了,只有在需要的时候才会去申请,而且每次申请一定是一个slab,slab固定是1M,一个slab由若干个chunk组成,每个chunk中保存着一个item结构体,一对(key,value),虽然在同一个slab中chunk 大小是相等的,但是在不同的slab中chunk的大小不一定相同,memcached根据chunk的大小不同,可以把slab分成好多类,在启动memcached可以通过-vv参数来看具体分为了多少个class.

 37 typedef struct { 38     unsigned int size;      /* sizes of items */ 39     unsigned int perslab;   /* how many items per slab */ 40  41     void **slots;           /* list of item ptrs */ 42     unsigned int sl_total;  /* size of previous array */ 43     unsigned int sl_curr;   /* first free slot */ 44  45     void *end_page_ptr;         /* pointer to next free item at end of page, or 0 */ 46     unsigned int end_page_free; /* number of items remaining at end of last alloced page */ 47  48     unsigned int slabs;     /* how many slabs were allocated for this class */ 49  50     void **slab_list;       /* array of slab pointers */ 51     unsigned int list_size; /* size of prev array */ 52  53     unsigned int killing;  /* index+1 of dying slab, or zero if none */ 54 } slabclass_t;通过输入以下命令可以知道有多个slabclss,也可以知道每个chunk的大小。 
lxn@lxn-Inspiron-3442:/usr/local/bin$ ./memcached -m 64 -p 9999 -d -vv<4 send buffer was 212992, now 268435456lxn@lxn-Inspiron-3442:/usr/local/bin$ slab class   1: chunk size     96 perslab 10922slab class   2: chunk size    120 perslab  8738slab class   3: chunk size    152 perslab  6898slab class   4: chunk size    192 perslab  5461slab class   5: chunk size    240 perslab  4369slab class   6: chunk size    304 perslab  3449slab class   7: chunk size    384 perslab  2730slab class   8: chunk size    480 perslab  2184slab class   9: chunk size    600 perslab  1747slab class  10: chunk size    752 perslab  1394slab class  11: chunk size    944 perslab  1110slab class  12: chunk size   1184 perslab   885slab class  13: chunk size   1480 perslab   708slab class  14: chunk size   1856 perslab   564slab class  15: chunk size   2320 perslab   451slab class  16: chunk size   2904 perslab   361slab class  17: chunk size   3632 perslab   288slab class  18: chunk size   4544 perslab   230slab class  19: chunk size   5680 perslab   184slab class  20: chunk size   7104 perslab   147slab class  21: chunk size   8880 perslab   118slab class  22: chunk size  11104 perslab    94slab class  23: chunk size  13880 perslab    75slab class  24: chunk size  17352 perslab    60slab class  25: chunk size  21696 perslab    48slab class  26: chunk size  27120 perslab    38slab class  27: chunk size  33904 perslab    30slab class  28: chunk size  42384 perslab    24slab class  29: chunk size  52984 perslab    19slab class  30: chunk size  66232 perslab    15slab class  31: chunk size  82792 perslab    12slab class  32: chunk size 103496 perslab    10slab class  33: chunk size 129376 perslab     8slab class  34: chunk size 161720 perslab     6slab class  35: chunk size 202152 perslab     5slab class  36: chunk size 252696 perslab     4slab class  37: chunk size 315872 perslab     3slab class  38: chunk size 394840 perslab     2slab class  39: chunk size 493552 perslab     2






1.2内存申请分配

memcached内存分配采用预分配,分组管理的方式,分组管理就是slabclass,预分配机制是这样的:首先向memcached添加一个item时,先计算item 的大小,来选择合适的slabclass,计算要放入的slabclass之后,在查看该类的chunk还有没有空闲,如果没有空闲的就会申请1M的空间并划分为该种类的chunk.

static int grow_slab_list (unsigned int id) {                                                  147     slabclass_t *p = &slabclass[id];148     if (p->slabs == p->list_size) {149         size_t new_size =  p->list_size ? p->list_size * 2 : 16;150         void *new_list = realloc(p->slab_list, new_size*sizeof(void*));151         if (new_list == 0) return 0;152         p->list_size = new_size;153         p->slab_list = new_list;154     }155     return 1;156 }157 

LRU和expired item
memcache并不会监视和清理过期数据,而是在客户端get时检查,称为lazy expiration。
item被检测到超时并不会被删除,而是放入slab->slots头部;
do_item_get --
   --判断该item是否过期
   do_item_unlink(it, hv);//将item从hashtable和LRU链中移除             
   do_item_remove(it);//删除item 
do_item_remove
  item_free(it);//释放item  
    slabs_free(it, ntotal, clsid);//slabclass结构执行释放 
           do_slabs_free(ptr, size, id);//执行释放 
以下是do_slabs_free的代码,将expired item放入slab->slots的头部       
    it = (item *)ptr;  
    it->it_flags |= ITEM_SLABBED;//修改item的状态标识,修改为空闲  
    it->prev = 0;//断开数据链表  
    it->next = p->slots;  
    if (it->next) it->next->prev = it;  
    p->slots = it; 

问:expired item何时被重用?
1 slab在新加item时会先查看LRU队尾;
2 如果队尾的item恰巧超时则重用,否则执行slabs_alloc;这一过程循环5次,若还没有找到可用item,则再次调用slabs_alloc;
slabs_alloc依次尝试  a slab->slot即expired item链表;  b slab->end_page_ptr 最后一个页面的空闲item; c 分配新的内存页
也就是说,只有LRU最后的5个元素状态为expired时,才有机会直接重用LRU,否则会依次尝试expired item list和slab的最后一个内存页的free item;

以下是代码实现
 
当客户端执行add操作,即往slab添加item时,调用do_item_alloc;
do_item_alloc
    mutex_lock(&cache_lock);//执行LRU锁 存储时,会尝试从LRU中选择合适的空间的空间  
    int tries = 5;//如果LRU中尝试5次还没合适的空间,则执行申请空间的操作 
    search = tails[id];//第id个LRU表的尾部 
    
    /* We walk up *only* for locked items. Never searching for expired
    for (; tries > 0 && search != NULL; tries--, search=search->prev) {
        uint32_t hv = hash(ITEM_key(search), search->nkey, 0);//获取分段锁 

        if ((search->exptime != 0 && search->exptime < current_time)  || (search->time <= oldest_live && oldest_live <= current_time)) { //过期时间的判断  
            it = search; 
        } else if ((it = slabs_alloc(ntotal, id)) == NULL) {//申请合适的slabclass  
          ......
        } 
        break;  
    } 

    if (!tried_alloc && (tries == 0 || search == NULL))//5次循环查找,未找到合适的空间  
        it = slabs_alloc(ntotal, id);//则从内存池申请新的空间 

    return it;  

注:每次新分配item时,先进行5次循环:检查LRU尾部item是否过期,过期则重用,否则尝试slabs_alloc申请新内存;  若5次后仍未获取可用内存,则再次尝试slabs_alloc申请内存;
http://blog.csdn.net/lcli2009/article/details/22091167

slabs_alloc
       pthread_mutex_lock(&slabs_lock);
    ret = do_slabs_alloc(size, id);
    pthread_mutex_unlock(&slabs_lock);

do_slabs_alloc(const size_t size, unsigned int id)
    p = &slabclass[id];  
    /* 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 */  
    // 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;  
}  

do_slabs_newslab--即为该slab新分配一个数据页;
    int len = p->size * p->perslab; 
    memory_allocate((size_t)len))



原创粉丝点击