memcached 中有两张hash 表,一个是“主hash 表”(primary_hashtable),另外一个是“原hash 表”(old_hashtable)。一般情况下都在主表中接受操作,在插入新item时判断是否需要进行扩;每次操作的时候,先会检测表是否正处于扩展(expanding)状态,如果是,则原表中进行操作,当扩容完成在转移到主表中进行操作。 在扩容时,采取逐步迁移策略:即每次只从原表中迁移一个bucket节点的item到新主表中,进行逐步迁移。

总的来看,这与Redis中的hash操作几乎一致。因此不再做详细讲解,具体分析见代码注释。

//hash表的初始化,参数hashtable_init为所设置的hashpower大小(阶数),默认大小为16
void assoc_init(const int hashtable_init) {
    if (hashtable_init) {
        hashpower = hashtable_init;
    }
 //创建主表(hashsize(hashpower):计算bucket节点数目=2的hashpower次方)
    primary_hashtable = calloc(hashsize(hashpower), sizeof(void *));
    if (! primary_hashtable) {
        fprintf(stderr, "Failed to init hashtable.\n");
        exit(EXIT_FAILURE);
    }
 //emcached内部有很多全局的统计信息,用于实时获取各个资源的使用情况,
 //对统计信息的更新都需要加锁
    STATS_LOCK();//对全局统计信息加锁,已更新信息
    stats.hash_power_level = hashpower;
    stats.hash_bytes = hashsize(hashpower) * sizeof(void *);
    STATS_UNLOCK();//解锁
}

//在哈希表中查找给定key的item:找到对应的哈希表,再找对应的桶节点,最后遍历链表找到目标key的item
item *assoc_find(const char *key, const size_t nkey, const uint32_t hv) {
    item *it;//桶节点
    unsigned int oldbucket;//在原表中的桶节点索引

//正在扩容,且当前节点在愿表中,还未迁移到主表
 //注意:i&(2^n-1)结果即为i除以2^n的余数
    if (expanding &&
        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
    {
        it = old_hashtable[oldbucket];
    } else {//没有扩容,或者已经迁移到主表中
        it = primary_hashtable[hv & hashmask(hashpower)];
    }

item *ret = NULL;
    int depth = 0;//目标节点在桶中的深度
    while (it) {//遍历桶节点链表
        if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == 0)) {
            ret = it;
            break;
        }
        it = it->h_next;
        ++depth;
    }
    MEMCACHED_ASSOC_FIND(key, nkey, depth);
    return ret;
}

/* returns the address of the item pointer before the key.  if *item == 0,
  the item wasn't found */
//内部函数:返回目标key item的前一个item的指针,这样在删除目标item时只需要将该返回item指针的next指针指向目标item的next item即可。
static item** _hashitem_before (const char *key, const size_t nkey, const uint32_t hv) {
    item **pos;
    unsigned int oldbucket;

if (expanding &&
        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
    {
        pos = &old_hashtable[oldbucket];
    } else {
        pos = &primary_hashtable[hv & hashmask(hashpower)];
    }

while (*pos && ((nkey != (*pos)->nkey) || memcmp(key, ITEM_key(*pos), nkey))) {
        pos = &(*pos)->h_next;
    }
    return pos;
}

/* grows the hashtable to the next power of 2. */
//哈希表扩容为原来的2倍(将原来的主表拷贝到久表中,对主表扩容)
static void assoc_expand(void) {
    old_hashtable = primary_hashtable;

primary_hashtable = calloc(hashsize(hashpower + 1), sizeof(void *));
    if (primary_hashtable) {
        if (settings.verbose > 1)
            fprintf(stderr, "Hash table expansion starting\n");
        hashpower++;
        expanding = true;
        expand_bucket = 0;
        STATS_LOCK();
        stats.hash_power_level = hashpower;
        stats.hash_bytes += hashsize(hashpower) * sizeof(void *);
        stats.hash_is_expanding = 1;
        STATS_UNLOCK();
    } else {
        primary_hashtable = old_hashtable;
        /* Bad news, but we can keep running. */
    }
}

static void assoc_start_expand(void) {
    if (started_expanding)
        return;
    started_expanding = true;
    pthread_cond_signal(&maintenance_cond);
}

/* Note: this isn't an assoc_update.  The key must not already exist to call this */
//将给定item插入到哈希表的桶的头部中  注意:该item不能已经存在于hash表中(hv:哈希值)
int assoc_insert(item *it, const uint32_t hv) {
    unsigned int oldbucket;

//    assert(assoc_find(ITEM_key(it), it->nkey) == 0);  /* shouldn't have duplicately named things defined */

//正在扩容,还未完成,则将该item放到原hashtable的对应bucket的单链表的头部
    if (expanding &&  
        (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)//注意hashpower已经加倍,因此是hashpower-1
    {
        it->h_next = old_hashtable[oldbucket];
        old_hashtable[oldbucket] = it;
    } else {//没有正在扩容则放到主hashtable中
        it->h_next = primary_hashtable[hv & hashmask(hashpower)];
        primary_hashtable[hv & hashmask(hashpower)] = it;
    }

hash_items++;
 //是否需要开始扩容
    if (! expanding && hash_items > (hashsize(hashpower) * 3) / 2) {
        assoc_start_expand();
    }

MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey, hash_items);
    return 1;
}

//删除对应item(只是将item从桶链表中移除)
void assoc_delete(const char *key, const size_t nkey, const uint32_t hv) {
    item **before = _hashitem_before(key, nkey, hv);//查找该item的前一个item

if (*before) {
        item *nxt;
        hash_items--;//hash表中的item总数
        /* The DTrace probe cannot be triggered as the last instruction
        * due to possible tail-optimization by the compiler
        */
        MEMCACHED_ASSOC_DELETE(key, nkey, hash_items);
        nxt = (*before)->h_next;
        (*before)->h_next = 0;  /* probably pointless, but whatever. */
        *before = nxt;
        return;
    }
    /* Note:  we never actually get here.  the callers don't delete things
      they can't find. */
    assert(*before != 0);
}

//迁移函数start_assoc_maintenance_thread(),创建迁移线程,调用函数assoc_maintenance_thread进行迁移
//线程函数:迁移bucket节点,默认一次迁移一个bucket
static void *assoc_maintenance_thread(void *arg) {

while (do_run_maintenance_thread) {
        int ii = 0;

/* Lock the cache, and bulk move multiple buckets to the new
        * hash table. */
        item_lock_global();
        mutex_lock(&cache_lock);

for (ii = 0; ii < hash_bulk_move && expanding; ++ii) {
            item *it, *next;
            int bucket;

for (it = old_hashtable[expand_bucket]; NULL != it; it = next) {
                next = it->h_next;

//计算哈希值,并计算得桶节点索引值
                bucket = hash(ITEM_key(it), it->nkey) & hashmask(hashpower);
                it->h_next = primary_hashtable[bucket];
                primary_hashtable[bucket] = it;
            }

//每迁移完一个bucket,就在久表中移除该bucket
            old_hashtable[expand_bucket] = NULL;

expand_bucket++;
   //扩容结束
            if (expand_bucket == hashsize(hashpower - 1)) {
                expanding = false;
                free(old_hashtable);
                STATS_LOCK();
                stats.hash_bytes -= hashsize(hashpower - 1) * sizeof(void *);
                stats.hash_is_expanding = 0;
                STATS_UNLOCK();
                if (settings.verbose > 1)
                    fprintf(stderr, "Hash table expansion done\n");
            }
        }

mutex_unlock(&cache_lock);
        item_unlock_global();

if (!expanding) {
            /* finished expanding. tell all threads to use fine-grained locks */
            switch_item_lock_type(ITEM_LOCK_GRANULAR);
            slabs_rebalancer_resume();
            /* We are done expanding.. just wait for next invocation */
            mutex_lock(&cache_lock);
            started_expanding = false;
            pthread_cond_wait(&maintenance_cond, &cache_lock);
            /* Before doing anything, tell threads to use a global lock */
            mutex_unlock(&cache_lock);
            slabs_rebalancer_pause();
            switch_item_lock_type(ITEM_LOCK_GLOBAL);
            mutex_lock(&cache_lock);
            assoc_expand();
            mutex_unlock(&cache_lock);
        }
    }
    return NULL;
}

分布式缓存系统 Memcached 哈希表操作的更多相关文章

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

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

  2. 分布式缓存系统 Memcached slab和item的主要操作

    上节在分析slab内存管理机制时分析Memcached整个Item存储系统的初始化过程slabs_init()函数:分配slabclass数组空间,到最后将各slab划分为各种级别大小的空闲item并 ...

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

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

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

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

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

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

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

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

  7. 分布式缓存系统Memcached[分享]

    个人网站:http://www.51pansou.com memcached视频下载:memcached视频教程 memcached源码下载:memcached源码 Memcached是什么? Mem ...

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

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

  9. memcached哈希表操作主要逻辑笔记

    以下注释的源代码都在memcached项目的assoc.c文件中 /* how many powers of 2's worth of buckets we use */ unsigned int h ...

随机推荐

  1. gitlab库迁移

    gitlab 迁移 gitlab上一共有两个分之,一级提交记录. git clone --bare http://111.222.333.xxx/jiqing/test.git 执行成功后,会多一个t ...

  2. VIM操作基础命令

    VIM操作基础命令 1 文件操作 1.1 打开文件 VIM 使用 –o 或 –O 选项打开多个文件,其中 –o 表示垂直并排,例如 vim -o lesson4 lesson5 lesson6.大O表 ...

  3. oracle数据库插入优化

    通过程序要把1000万的数据插入到数据表中,刚开始每100条数据耗时50ms左右,但是越往后越慢,最慢到了十几秒的都有,真实好坑了. 于是在网上百度了一波,如何进行insert优化.倒是有了一点小小的 ...

  4. Pycharm for mac 快捷键

    cmd b 跳转到声明处(cmd加鼠标) opt + 空格 显示符号代码 (esc退出窗口 回车进入代码) cmd []光标之前/后的位置 opt + F7 find usage cmd backsp ...

  5. spring: spittr实例 构建简单的web应用 Test测试用例

    本例为Test,测试上一贴的程序 package spittr.web; import org.junit.Test; import org.springframework.test.web.serv ...

  6. Selenium with Python 001 - 安装篇

    Selenium Python bindings 提供了一个简单的API,让你使用Selenium WebDriver来编写功能/校验测试. 通过Selenium Python的API,你可以非常直观 ...

  7. sizeof结构体

    规则1:结构体的对折长度为其基本数据成员的长度的最大值. 规则2:指定边界情况下,结构体的对折长度为自身对折长度和指定对折长度中较小者. 规则3:当行内结构体的基本数据成员的起始地址必须为其长度的整数 ...

  8. IOS-导航路线

    1.可以将需要导航的位置丢给系统自带的APP进行导航 2.发送网络请求到公司服务器获取导航数据, 然后自己手动绘制导航 3.利用三方SDK实现导航(百度) >当点击开始导航时获取用户输入的起点和 ...

  9. Django的 CBV和FBV

    FBV CBV 回顾多重继承和Mixin 回到顶部 FBV FBV(function base views) 就是在视图里使用函数处理请求. 在之前django的学习中,我们一直使用的是这种方式,所以 ...

  10. Agilent RF fundamentals (4)- Impedance match and distortions

    1 Impedance match: 2 distortions: Solar radiation produces background noise