上节在分析slab内存管理机制时分析Memcached整个Item存储系统的初始化过程slabs_init()函数:分配slabclass数组空间,到最后将各slab划分为各种级别大小的空闲item并挂载到对应大小slab的空闲链表slots上。本节将继续分析对slab和item的主要操作过程。

slab机制中所采用的LRU算法:

在memcached运行过程中,要把一个item调入内存,但内存已无空闲空间时,为了保证程序能正常运行,系统必须从内存中调出一部分数据,送磁盘的对换区中。但应将哪些数据调出,须根据一定的算法来确定。通常,把选择换出数据(页面)的算法称为页面置换算法(Page Replacement Algorithms)。 Memcached采用最近最久未使用(LRU)置换算法,是根据数据(页面)调入内存后的使用情况进行决策的。由于无法预测各页面将来的使用情况,只能利用“最近的过去”作为“最近的将来”的近似,因此,LRU置换算法是选择最近最久未使用的页面予以淘汰。

每个slabclass维护一个双向链表,所有分配出去的item按照最近访问时间依次放到该链表中,该链表也就相当于LRU队列。所有slabclass的链表头 尾分表保存在*heads、 *tails两个数组中。当内存不足时,memcached会对应大小的slabclass维护的双向链表的尾部开始检测,即最近最久未使用的item,往前一直寻找合适的item予以淘汰。所以该LRU算法为局部的slabclass淘汰机制。但是在一些特定情形也会可能引起一些不必要的麻烦,可以在运行时加入”-M”参数禁止该算法。

Slab相关操作:

/*
 *根据给定的大小在slabclass数组中找到合适的slabclass,返回该slabclass的下标
 *如果给定大小为0,或者大于最大的slab的item大小,则返回0
 */
unsigned int slabs_clsid(const size_t size) {
    int res = POWER_SMALLEST;

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;
}

//当该slabcalss的slots空闲链表为空时为指定大小的item分配空间:
//size为给定的item大小,id为对应的slabclass编号,返回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) {//id编号不合法
        MEMCACHED_SLABS_ALLOCATE_FAILED(size, 0);
        return NULL;
    }

p = &slabclass[id];//获取对应的slabclass
    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 */
 //如果空闲链表为空且 为该大小的slab分配一个slabcalss(1M)失败
    if (! (p->sl_curr != 0 || do_slabs_newslab(id) != 0)) {
        /* We don't have more memory available */
        ret = NULL;//如果所有空间都用尽的时候,则返回NULL表示目前资源已经枯竭了。
    } else if (p->sl_curr != 0) {//不管是原来的空闲item或者新分配的item,都将挂在到slots上
        /* return off our freelist */
        it = (item *)p->slots;//返回空闲链表的头item
        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;
}
//创建空闲item ,挂载到对应slabclass的空闲链表中  
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);//判断id有效性  
    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;  
    it->prev = 0;  
    it->next = p->slots;//挂载到slabclass的空闲链表中  
    if (it->next) it->next->prev = it;  
    p->slots = it;  
  
    p->sl_curr++;//空闲item个数+1  
    p->requested -= size;//已经申请到的空间数量更新  
    return;  
}

Item相关操作:

主要接口函数:

do_item_alloc() :

首先调用slabs_clsid给item归类,然后调用slabs_alloc()函数分配内存,当可使用空间枯竭了的时候,就开始使用LRU算法了,从一个对应大小的尾部tails[id]开始,向前不断尝试能否释放,当发现一个item当前没有被使用(引用计数refcount为0),且其生存时间已经为0或生存时间大于现在时间,就果断的把它释放掉。并再次调用slabs_alloc(),作者在此提到有一种非常罕见的bug能够使引用计数(refcount)发生泄漏,处理方法是,当item连续保持锁定状态3小时以上,说明它已经足够陈旧了,应该果断将其释放。最后再将数据复制到分配的空间内。

item_free():

调用slabs_free将其释放。

do_item_link():

1.调用assoc_insert()将item指针插入hash表中

2.调用get_cas_id()给it的cas_id赋值。

3.调用item_link_q(),把该item插入LRU队列的最前面

do_item_unlink ():
1.调用assoc_delete()在hash表中删除此item

2.调用item_unlink_q()从该slab class去除此item的连接,此处考虑了item在链表头部,尾部等各种情况。

3.最后当引用计数为0的时候,调用item_free()将其释放。

do_item_remove () :

减少引用计数refcount,当发现引用计数为0的时候,就将其释放。

do_item_update ():

先调用item_unlink_q(),更新了时间以后,再调用item_link_q()。将其重新连接到LRU队列之中,即让该item移到LRU队列的最前。

do_item_replace ():

调用do_item_unlink()解除原有item的连接,再调用do_item_link()连接到新的item。

item_get () :

值得说明的是,memcached的懒惰删除逻辑在这里有体现。就是当你需要get 一个item的时候才考虑该item是否应该删除。 首先调用do_item_get_notedeleted()函数,根据关键字使用assoc_find()查找item,如果没找到,返回NULL,再判断是否达到最大的生存时间,如果是的话,就do_item_unlink该item,返回NULL。 如果该item没有满足删除条件,将其引用计数加1,并返回该item的指针。

具体分析见代码:

//key:键 nkey:键的长度 falgs:键falg    exptime:过期时间(相对) cur_hv哈希值
item *do_item_alloc(char *key, const size_t nkey, const int flags,
                    const rel_time_t exptime, const int nbytes,
                    const uint32_t cur_hv) {
    uint8_t nsuffix;//key的后缀长度
    item *it = NULL;
    char suffix[40];//存放key后缀
 //该item所需的总长度
    size_t ntotal = item_make_header(nkey + 1, flags, nbytes, suffix, &nsuffix);
    if (settings.use_cas) {
        ntotal += sizeof(uint64_t);
    }

//以item总长来计算所属的slabclass
    unsigned int id = slabs_clsid(ntotal);
    if (id == 0)
        return 0;

mutex_lock(&cache_lock);
    /* do a quick check if we have any expired items in the tail.. */
    int tries = 5;//执行5次尝试检查,(快速,而不是全部都检查一遍)
    /* Avoid hangs if a slab has nothing but refcounted stuff in it. */
 //将所有过期时间旧于设定的oldest_live时间的所有item视为过期item,以避免那些实际没用,但却被一直引用的item
    int tries_lrutail_reflocked = 1000;
    int tried_alloc = 0;
    item *search;
    item *next_it;
    void *hold_lock = NULL;
    rel_time_t oldest_live = settings.oldest_live;//对所有item设定的item最长生存时间

search = tails[id];//serch指向该slabclass对应的item链表尾部
    /* We walk up *only* for locked items. Never searching for expired.
    * Waste of CPU for almost all deployments */
    for (; tries > 0 && search != NULL; tries--, search=next_it) {
        /* we might relink search mid-loop, so search->prev isn't reliable */
        next_it = search->prev;
        if (search->nbytes == 0 && search->nkey == 0 && search->it_flags == 1) {
            /* We are a crawler, ignore it. */
            tries++;
            continue;
        }
  //计算哈希值,用于锁定serch的该item,避免其他调用者增加该item的引用计数
        uint32_t hv = hash(ITEM_key(search), search->nkey);
        /* Attempt to hash item lock the "search" item. If locked, no
        * other callers can incr the refcount
        */
        /* Don't accidentally grab ourselves, or bail if we can't quicklock */
  //如果该item正好是当前的item,则忽略
        if (hv == cur_hv || (hold_lock = item_trylock(hv)) == NULL)
            continue;
        /* Now see if the item is refcount locked */
        if (refcount_incr(&search->refcount) != 2) {
            /* Avoid pathological case with ref'ed items in tail */
            do_item_update_nolock(search);
            tries_lrutail_reflocked--;
            tries++;
            refcount_decr(&search->refcount);
            itemstats[id].lrutail_reflocked++;
            /* Old rare bug could cause a refcount leak. We haven't seen
            * it in years, but we leave this code in to prevent failures
            * just in case */
            if (settings.tail_repair_time &&
                    search->time + settings.tail_repair_time < current_time) {
                itemstats[id].tailrepairs++;
                search->refcount = 1;
                do_item_unlink_nolock(search, hv);
            }
            if (hold_lock)
                item_trylock_unlock(hold_lock);

if (tries_lrutail_reflocked < 1)
                break;

continue;
        }

/* Expired or flushed */
  ////还没过期,或者已经过期但是最近访问时间小于设定的最大过期时间,并且设定的最大过期时间小于当前时间
        if ((search->exptime != 0 && search->exptime < current_time)
            || (search->time <= oldest_live && oldest_live <= current_time))
      {
            itemstats[id].reclaimed++;
            if ((search->it_flags & ITEM_FETCHED) == 0) {
                itemstats[id].expired_unfetched++;
            }
            it = search;
            slabs_adjust_mem_requested(it->slabs_clsid, ITEM_ntotal(it), ntotal);
            do_item_unlink_nolock(it, hv);//从哈希表  及item链表中删除,递减引用计数
            /* Initialize the item block: */
            it->slabs_clsid = 0;//注意:slabs_clsid = 0表明不属于任何slabclass
        } else if ((it = slabs_alloc(ntotal, id)) == NULL) {
            tried_alloc = 1;
            if (settings.evict_to_free == 0) {
                itemstats[id].outofmemory++;
            } else {
                itemstats[id].evicted++;
                itemstats[id].evicted_time = current_time - search->time;
                if (search->exptime != 0)
                    itemstats[id].evicted_nonzero++;
                if ((search->it_flags & ITEM_FETCHED) == 0) {
                    itemstats[id].evicted_unfetched++;
                }
                it = search;
                slabs_adjust_mem_requested(it->slabs_clsid, ITEM_ntotal(it), ntotal);
                do_item_unlink_nolock(it, hv);
                /* Initialize the item block: */
                it->slabs_clsid = 0;

/* If we've just evicted an item, and the automover is set to
                * angry bird mode, attempt to rip memory into this slab class.
                * TODO: Move valid object detection into a function, and on a
                * "successful" memory pull, look behind and see if the next alloc
                * would be an eviction. Then kick off the slab mover before the
                * eviction happens.
                */
                if (settings.slab_automove == 2)
                    slabs_reassign(-1, id);
            }
        }

refcount_decr(&search->refcount);
        /* If hash values were equal, we don't grab a second lock */
        if (hold_lock)
            item_trylock_unlock(hold_lock);
        break;
    }

if (!tried_alloc && (tries == 0 || search == NULL))
        it = slabs_alloc(ntotal, id);

if (it == NULL) {
        itemstats[id].outofmemory++;
        mutex_unlock(&cache_lock);
        return NULL;
    }

assert(it->slabs_clsid == 0);
    assert(it != heads[id]);

/* Item initialization can happen outside of the lock; the item's already
    * been removed from the slab LRU.
    */
 //初始化item。 由于该item已不在lru队列中,因此可以无锁操作
    it->refcount = 1;    //引用计数初始化为1 /* the caller will have a reference */
    mutex_unlock(&cache_lock);
    it->next = it->prev = it->h_next = 0;
    it->slabs_clsid = id;//其所属id已经计算好

DEBUG_REFCNT(it, '*');
    it->it_flags = settings.use_cas ? ITEM_CAS : 0;
    it->nkey = nkey;//
    it->nbytes = nbytes;//
    memcpy(ITEM_key(it), key, nkey);
    it->exptime = exptime;
    memcpy(ITEM_suffix(it), suffix, (size_t)nsuffix);
    it->nsuffix = nsuffix;
    return it;
}

//释放一个item,挂载到slots的头部
void item_free(item *it) {
    size_t ntotal = ITEM_ntotal(it);
    unsigned int clsid;
    assert((it->it_flags & ITEM_LINKED) == 0);
    assert(it != heads[it->slabs_clsid]);
    assert(it != tails[it->slabs_clsid]);
    assert(it->refcount == 0);

/* so slab size changer can tell later if item is already free or not */
    clsid = it->slabs_clsid;
    it->slabs_clsid = 0;
    DEBUG_REFCNT(it, 'F');
    slabs_free(it, ntotal, clsid);
}

//将item插入到哈希表  lru队列
int do_item_link(item *it, const uint32_t hv) {
    MEMCACHED_ITEM_LINK(ITEM_key(it), it->nkey, it->nbytes);
    assert((it->it_flags & (ITEM_LINKED|ITEM_SLABBED)) == 0);
    mutex_lock(&cache_lock);
    it->it_flags |= ITEM_LINKED;
    it->time = current_time;

STATS_LOCK();
    stats.curr_bytes += ITEM_ntotal(it);
    stats.curr_items += 1;
    stats.total_items += 1;
    STATS_UNLOCK();

/* Allocate a new CAS ID on link. */
    ITEM_set_cas(it, (settings.use_cas) ? get_cas_id() : 0);
    assoc_insert(it, hv);//将该item指针插入到hash表的对应bucket的头部(正常情况下插入到主表,扩容时插入到原表)
    item_link_q(it);//将该item指针插入到对应slab的头部
    refcount_incr(&it->refcount);//递增引用计数
    mutex_unlock(&cache_lock);

return 1;
}

//将该item从哈希表  lru队列中删除,递减引用计数
void do_item_unlink(item *it, const uint32_t hv) {
    MEMCACHED_ITEM_UNLINK(ITEM_key(it), it->nkey, it->nbytes);
    mutex_lock(&cache_lock);
    if ((it->it_flags & ITEM_LINKED) != 0) {
        it->it_flags &= ~ITEM_LINKED;
        STATS_LOCK();
        stats.curr_bytes -= ITEM_ntotal(it);
        stats.curr_items -= 1;
        STATS_UNLOCK();
        assoc_delete(ITEM_key(it), it->nkey, hv);//在哈希表中删除该item
        item_unlink_q(it);//在slab中删除该item
        do_item_remove(it);//如果item引用计数减到0,则释放,挂载到slots头部
    }
    mutex_unlock(&cache_lock);
}

//item引用计数为0,则释放
void do_item_remove(item *it) {
    MEMCACHED_ITEM_REMOVE(ITEM_key(it), it->nkey, it->nbytes);
    assert((it->it_flags & ITEM_SLABBED) == 0);
    assert(it->refcount > 0);

if (refcount_decr(&it->refcount) == 0) {
        item_free(it);
    }
}

//注意:避免过于频繁的获取该item带来的性能影响,设定最小更新时间ITEM_UPDATE_INTERVAL:
//即至少要这么久才更新一次该item在lru中的位置
//先从lru链表删除,然后更新访问时间,最后在插入到lru的头部
void do_item_update(item *it) {
    MEMCACHED_ITEM_UPDATE(ITEM_key(it), it->nkey, it->nbytes);
 //是否达到更新的要求
    if (it->time < current_time - ITEM_UPDATE_INTERVAL) {
        assert((it->it_flags & ITEM_SLABBED) == 0);

mutex_lock(&cache_lock);
        if ((it->it_flags & ITEM_LINKED) != 0) {
            item_unlink_q(it);//从lru链表删除
            it->time = current_time;//更新最近访问时间
            item_link_q(it);//更新位置:放到lru头部
        }
        mutex_unlock(&cache_lock);
    }
}

//以新的item替换给定的item(哈希表  lru)
int do_item_replace(item *it, item *new_it, const uint32_t hv) {
    MEMCACHED_ITEM_REPLACE(ITEM_key(it), it->nkey, it->nbytes,
                          ITEM_key(new_it), new_it->nkey, new_it->nbytes);
    assert((it->it_flags & ITEM_SLABBED) == 0);

do_item_unlink(it, hv);//将旧的item从哈希表  lru队列去掉
    return do_item_link(new_it, hv);//将该新的item加入哈希表  lru中
}

//如果该item过期则返回NULL,且同时在哈希表、lru中删除。(get中体现的正是惰性删除机制)
item *do_item_get(const char *key, const size_t nkey, const uint32_t hv) {
    //mutex_lock(&cache_lock);
    item *it = assoc_find(key, nkey, hv);//在哈希表中查找该item,然后利用该item的clsid信息去lru中查找该item
    if (it != NULL) {
        refcount_incr(&it->refcount);
        /* Optimization for slab reassignment. prevents popular items from
        * jamming in busy wait. Can only do this here to satisfy lock order
        * of item_lock, cache_lock, slabs_lock. */
        if (slab_rebalance_signal &&
            ((void *)it >= slab_rebal.slab_start && (void *)it < slab_rebal.slab_end)) {
            do_item_unlink_nolock(it, hv);
            do_item_remove(it);//递减引用计数
            it = NULL;
        }
    }
    //mutex_unlock(&cache_lock);
    int was_found = 0;

if (settings.verbose > 2) {
        int ii;
        if (it == NULL) {
            fprintf(stderr, "> NOT FOUND ");
        } else {
            fprintf(stderr, "> FOUND KEY ");
            was_found++;
        }
        for (ii = 0; ii < nkey; ++ii) {
            fprintf(stderr, "%c", key[ii]);
        }
    }

if (it != NULL) {
        if (settings.oldest_live != 0 && settings.oldest_live <= current_time &&
            it->time <= settings.oldest_live) {//超过最大生存时间,则从lru中删除
            do_item_unlink(it, hv);
            do_item_remove(it);
            it = NULL;
            if (was_found) {
                fprintf(stderr, " -nuked by flush");
            }
        } else if (it->exptime != 0 && it->exptime <= current_time) {//过期
            do_item_unlink(it, hv);
            do_item_remove(it);
            it = NULL;
            if (was_found) {
                fprintf(stderr, " -nuked by expire");
            }
        } else {
            it->it_flags |= ITEM_FETCHED;
            DEBUG_REFCNT(it, '+');
        }
    }

if (settings.verbose > 2)
        fprintf(stderr, "\n");

return it;
}

经过前面的几节分析,到此对于Memcached中的内存操作,数据item的存储体系即基本存取操作等基础部分的剖析已完成。 
接下来将主要分析Memcached中的核心部分:I/O框架库Libevent的具体应用,以及多线程的处理机制等。这部分也正是前段时间刚接触的内容,因此接下来将给出更为详细的讲解。

分布式缓存系统 Memcached slab和item的主要操作的更多相关文章

  1. 分布式缓存系统 Memcached 整体架构

    分布式缓存系统 Memcached整体架构 Memcached经验分享[架构方向] Memcached 及 Redis 架构分析和比较

  2. 分布式缓存系统 Memcached 状态机之SET、GET命令

    首先对状态机中的各种状态做个简单总结,具体可见状态转换示意图: 1.listening:这个状态是主线程的默认状态,它只有这一个状态:负责监听socket,接收客户连接,将连接socket派发给工作线 ...

  3. (转)C# 中使用分布式缓存系统Memcached

    转自:http://blog.csdn.net/devgis/article/details/8212917 缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了 ...

  4. 分布式缓存系统Memcached简介与以及在.net下的实践(转)

    缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...

  5. 分布式缓存系统 Memcached 基本配置与命令

    为了方便测试,给出一个C客户端libmemcached链接:https://launchpad.net/libmemcached/ 以及memcacheclient-2.0 : http://code ...

  6. 分布式缓存系统 Memcached 快速入门

    Memcached介绍   官网地址      Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提 ...

  7. 分布式缓存系统Memcached简介与实践

    缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...

  8. 分布式缓存系统Memcached简介与实践(.NET memcached client library)

    缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...

  9. [Memcached]分布式缓存系统Memcached在Asp.net下的应用

    Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.Memcached ...

随机推荐

  1. Pandas基本功能

    到目前为止,我们了解了三种Pandas数据结构以及如何创建它们.接下来将主要关注数据帧(DataFrame)对象,因为它在实时数据处理中非常重要,并且还讨论其他数据结构. 系列基本功能 编号 属性或方 ...

  2. 运行jar_测试代码

    1.Eclipse 将 工程 导出成 jar Eclipse --> Export... --> 界面中树状图形中选择" Java下的'JAR file' "(不知道这 ...

  3. hackerrankWeek of Code 32

    hackerrankWeek of Code 32 A.Duplication B.Fight the Monsters! C.Circular Walk D.Geometric Trick E.Ba ...

  4. vmware增加共享文件夹

    增加共享文件夹 VMWare提供共享文件夹功能.前提是在虚拟机中安装VMware tools 1. 安装VMware tools 会自动在虚拟机中的/media/VMware Tools/中有个压缩包 ...

  5. 今夜我们一起学习 Apache Shiro

    简介 Apache Shiro 是一个功能强大但又非常容易使用的 Java 安全框架,提供了认证,授权,加密以及会话管理功能.因为 Shiro 的 API 是非常容易理解的,所以使用 Shiro 你可 ...

  6. 前端读取Excel报表文件 js-xlsx

    1.http://www.cnblogs.com/imwtr/p/6001480.html (前端读取Excel报表文件) 2.https://github.com/SheetJS/js-xlsx

  7. SQLSERVER store procedure 临时表

    有些时候显示重复数据时,使用: ) 但有些时候表A过大或者逻辑复杂.显示数据时,会造成性能的影响,这时你就可以使用临时表:   ) create table #temp( XXX , XXX) )in ...

  8. shell编程学习2

    1.shell中调用linux命令(1)直接执行(2)反引号括起来执行.有时候我们在shell中调用linux命令是为了得到这个命令的返回值(结果值),这时候就适合用一对反引号(键盘上ESC按键下面的 ...

  9. mcake活动维护常见问题记录【wap端】 ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★

    ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ wap端问题及解决方法 ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ 一.wap端弹窗 .Dialogbg-Select{ background-co ...

  10. Ubuntu中修改Terminal背景

    哈哈哈哈,没什么卵用,只能是看起来舒服,有逼格! 详解参考