分布式缓存系统 Memcached 哈希表操作
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 哈希表操作的更多相关文章
- 分布式缓存系统 Memcached 整体架构
分布式缓存系统 Memcached整体架构 Memcached经验分享[架构方向] Memcached 及 Redis 架构分析和比较
- 分布式缓存系统 Memcached slab和item的主要操作
上节在分析slab内存管理机制时分析Memcached整个Item存储系统的初始化过程slabs_init()函数:分配slabclass数组空间,到最后将各slab划分为各种级别大小的空闲item并 ...
- 分布式缓存系统Memcached简介与实践
缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...
- 分布式缓存系统Memcached简介与实践(.NET memcached client library)
缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...
- (转)C# 中使用分布式缓存系统Memcached
转自:http://blog.csdn.net/devgis/article/details/8212917 缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了 ...
- 分布式缓存系统Memcached简介与以及在.net下的实践(转)
缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...
- 分布式缓存系统Memcached[分享]
个人网站:http://www.51pansou.com memcached视频下载:memcached视频教程 memcached源码下载:memcached源码 Memcached是什么? Mem ...
- [Memcached]分布式缓存系统Memcached在Asp.net下的应用
Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.Memcached ...
- memcached哈希表操作主要逻辑笔记
以下注释的源代码都在memcached项目的assoc.c文件中 /* how many powers of 2's worth of buckets we use */ unsigned int h ...
随机推荐
- Sub-process /usr/bin/dpkg returned an error code (1) 如何解决
cd /var/lib/dpkg sudo mv info info.bak sudo mkdir info sudo dpkg --configure -a sudo apt-get install ...
- python学习笔记(接口自动化框架 V1.0)
之前是利用python自带的unittest测试框架 这次自己设计一个 之后再一点点往里面加功能 (ps:当然这个框架真的是很简单..很简单...很简单...) excel文件格式: #!/usr/b ...
- python 爬虫001-http请求过程
HTTP 请求流程 一次完整的HTTP请求过程从TCP三次握手建立连接成功后开始,客户端按照指定的格式开始向服务端发送HTTP请求,服务端接收请求后,解析HTTP请求,处理完业务逻辑,最后返回一个HT ...
- 使用Spring实现MySQL读写分离
1. 为什么要进行读写分离 大量的JavaWeb应用做的是IO密集型任务, 数据库的压力较大, 需要分流 大量的应用场景, 是读多写少, 数据库读取的压力更大 一个很自然的思路是使用一主多从的数据库集 ...
- 014对象——对象 __isset __unset __sleep __wakeup
<?php /** * */ /*class lantian { public $name; public $age; private $money; public $c; function _ ...
- QT中给程序加上主界面的图标
首先在源码目录下面新建一个 myapp.rc的文件,在里面填写如下: IDI_ICON1 ICON DISCARDABLE "myappico.ico" (名字看自己的图片,注意图 ...
- 【机器学习】Boosting和Bagging的差别
boosting和bagging的差别: bagging中的模型是强模型,偏差低,方差高.目标是降低方差.在bagging中,每个模型的bias和variance近似相同,但是互相相关性不太高,因此一 ...
- scorm标准的LMS在客户端的运行机制
1)运行SCORM APIAdapter. 2)调用API初始化函数. 3)加载课件SCO初始化数据. 4)获取Data Model中的用户ID和用户姓名. 5)获取Data Mode ...
- 人生苦短之我用Python篇(深浅拷贝、常用模块、内置函数)
深浅拷贝 有时候,尤其是当你在处理可变对象时,你可能想要复制一个对象,然后对其做出一些改变而不希望影响原来的对象.这就是Python的copy所发挥作用的地方. 定义了当对你的类的实例调用copy.c ...
- 转载:将STM32的标准库编译成lib使用【图文】
from:http://www.cnblogs.com/zyqgold/p/3189719.html 百度上边也有不少关于lib文件的文章,恰巧看到该博文,感觉该博文的条理清晰,步骤明确,故复制到这个 ...