memcached哈希表操作主要逻辑笔记
以下注释的源代码都在memcached项目的assoc.c文件中
/* how many powers of 2's worth of buckets we use */
unsigned int hashpower = HASHPOWER_DEFAULT; /* 哈希表bucket的级别,(1<<hashpower) == bucket的个数 */ /* Main hash table. This is where we look except during expansion. */
/**主要的哈希表, 用来存储memcached的key-value数据
* 扩容时会将数据暂存在另一个指针,之后重新分配空间,再以bucket为单位
* 将已有的数据迁移到这张表,所以这张表始终代表最新的数据
*/
static item** primary_hashtable = ; /*
* Previous hash table. During expansion, we look here for keys that haven't
* been moved over to the primary yet.
*/
static item** old_hashtable = ; /**原有的哈希表,只有在扩容时才会使用,保存原有的哈希表的数据 */ /* Number of items in the hash table. */
static unsigned int hash_items = ; /** 整个哈希表中item的个数*/ /* Flag: Are we in the middle of expanding now? */
static bool expanding = false; /** 标识是否正在扩容哈希表*/
static bool started_expanding = false; /* 是否已经开始扩容*/ /*
* During expansion we migrate values with bucket granularity; this is how
* far we've gotten so far. Ranges from 0 .. hashsize(hashpower - 1) - 1.
*/
/**
* 在扩容期间,数据的迁移是以bucket为单位进行迁移, expand_bucket表示迁移进行到第几个bucket
*/
static unsigned int expand_bucket = ; /**
* 哈希表的初始化
* 整个哈希表类似于一个二维数组,初始化分配bucket的空间,具体的item直接申请空间链接在bucket子链上(拉链法解决key冲突)
*/
void assoc_init(const int hashtable_init) {
if (hashtable_init) {
hashpower = hashtable_init;
}
/**初始化哈希表的存储空间*/
primary_hashtable = calloc(hashsize(hashpower), sizeof(void *));
if (! primary_hashtable) {
fprintf(stderr, "Failed to init hashtable.\n");
exit(EXIT_FAILURE);
}
STATS_LOCK();
stats.hash_power_level = hashpower;
stats.hash_bytes = hashsize(hashpower) * sizeof(void *);
STATS_UNLOCK();
} /**
* 根据key查找item
* hv 表示key的hash值
*/
item *assoc_find(const char *key, const size_t nkey, const uint32_t hv) {
item *it; //指向所属的bucket地址
unsigned int oldbucket;
//先判断是否正在扩容
if (expanding && //正在扩容时继续判断key所属的bucket是否已经迁移到old_hashtable
(oldbucket = (hv & hashmask(hashpower - ))) >= expand_bucket)
{
it = old_hashtable[oldbucket]; //已经迁移到old_hashtable则在这里查找bucket
} else {
it = primary_hashtable[hv & hashmask(hashpower)];//没有迁移或者尚未迁移所属的bucket
} item *ret = NULL;
int depth = ;
/** 循环比较拉链上的每个item的key值,相等则返回item的引用*/
while (it) {
if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == )) {
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 */
/**
* 查找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 - ))) >= 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倍buckets数量*/
static void assoc_expand(void) {
/** old_hashtable指向已有的primary_hashtable*/
old_hashtable = primary_hashtable; /**重新为 primary_hashtable分配空间*/
primary_hashtable = calloc(hashsize(hashpower + ), sizeof(void *));
if (primary_hashtable) { /** 分配成功*/
if (settings.verbose > )
fprintf(stderr, "Hash table expansion starting\n");
hashpower++;
expanding = true; /** 设置开始扩容标识*/
expand_bucket = ; /** 已迁移的bucket序号*/
STATS_LOCK();
stats.hash_power_level = hashpower;
stats.hash_bytes += hashsize(hashpower) * sizeof(void *);
stats.hash_is_expanding = ;
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->key尚未存在已有的哈希表*/
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 */ if (expanding &&
(oldbucket = (hv & hashmask(hashpower - ))) >= expand_bucket)
{ /** 正在扩容且对应的bucket尚未被迁移到primary_hashtable*/
it->h_next = old_hashtable[oldbucket];
old_hashtable[oldbucket] = it;
} else { /** 没有在扩容或者对应的bucket已经被迁移到primary_hashtable*/
it->h_next = primary_hashtable[hv & hashmask(hashpower)];
primary_hashtable[hv & hashmask(hashpower)] = it;
} // 更新hash_item的数量
hash_items++; /** 哈希表item的数量超过bucket数的3分之2, 这里表示只关心由于存储item数量增长必须引起的扩容*/
if (! expanding && hash_items > (hashsize(hashpower) * ) / ) {
assoc_start_expand(); //发送条件变量满足的信号
} MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey, hash_items);
return ;
}
/** 删除一个item*/
void assoc_delete(const char *key, const size_t nkey, const uint32_t hv) {
/** 找到指向item地址的指针*/
item **before = _hashitem_before(key, nkey, hv); if (*before) {
item *nxt;
hash_items--; /** 减少1个 */
/* 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 = ; /* 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 != );
} /** 标识是否需要执行维护线程主要逻辑*/
static volatile int do_run_maintenance_thread = ; #define DEFAULT_HASH_BULK_MOVE 1
int hash_bulk_move = DEFAULT_HASH_BULK_MOVE; /** 哈希表维护线程的主要逻辑*/
static void *assoc_maintenance_thread(void *arg) { /** 主线程未退出时,这里基本是进入一个无限循环*/
while (do_run_maintenance_thread) {
int ii = ; /* Lock the cache, and bulk move multiple buckets to the new
* hash table. */
/** 获取worker线程共享的item_global_lock锁,批量迁移buckets到新的哈希表*/
item_lock_global();
mutex_lock(&cache_lock); /** 这个循环默认只走一次,主要目的是不想过久的占用item全局锁,影响worker线程工作效率*/
for (ii = ; ii < hash_bulk_move && expanding; ++ii) {
item *it, *next;
int bucket; /** 对bucket上拉链的每一个item进行重新hash到primary_hashtable*/
for (it = old_hashtable[expand_bucket]; NULL != it; it = next) {
next = it->h_next; /** 重新计算所属的bucket*/
bucket = hash(ITEM_key(it), it->nkey) & hashmask(hashpower);
/** 加入到primary_hashtable对应bucket的头部*/
it->h_next = primary_hashtable[bucket];
primary_hashtable[bucket] = it;
} /** 将old_hashtable上已经被迁移的bucket置为NULL*/
old_hashtable[expand_bucket] = NULL; /** 递增迁移的bucket序号*/
expand_bucket++;
/** 判断是否已经迁移完*/
if (expand_bucket == hashsize(hashpower - )) {
expanding = false;
free(old_hashtable); //释放old_hashtable
STATS_LOCK();
stats.hash_bytes -= hashsize(hashpower - ) * sizeof(void *);
stats.hash_is_expanding = ;
STATS_UNLOCK();
if (settings.verbose > )
fprintf(stderr, "Hash table expansion done\n");
}
} /** 释放锁*/
mutex_unlock(&cache_lock);
/** 释放全局锁,这样worker线程才有机会获得锁进而服务用户请求,减少等待时间*/
item_unlock_global(); /** 未进行扩容或者扩容结束*/
if (!expanding) {
/** 通知其他线程使用细粒度的锁,通过线程pipe进行通信*/
/* finished expanding. tell all threads to use fine-grained locks */
switch_item_lock_type(ITEM_LOCK_GRANULAR); /**恢复slabs的自平衡锁,确保哈希表扩容不会与slabs重新分配同时进行*/
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没有正在进行重新分配*/
slabs_rebalancer_pause(); /**通过pipe的方式通知worker线程改变使用锁的粒度为全局锁*/
switch_item_lock_type(ITEM_LOCK_GLOBAL);
mutex_lock(&cache_lock);
/** 开始扩容*/
assoc_expand();
mutex_unlock(&cache_lock);
}
}
return NULL;
} static pthread_t maintenance_tid; /** 启动哈希表扩容监听线程*/
int start_assoc_maintenance_thread() {
int ret;
char *env = getenv("MEMCACHED_HASH_BULK_MOVE");
if (env != NULL) {
hash_bulk_move = atoi(env);
if (hash_bulk_move == ) {
hash_bulk_move = DEFAULT_HASH_BULK_MOVE;
}
}
/** 创建线程*/
if ((ret = pthread_create(&maintenance_tid, NULL,
assoc_maintenance_thread, NULL)) != ) {
fprintf(stderr, "Can't create thread: %s\n", strerror(ret));
return -;
}
return ;
} /** 停止扩容线程,基本是在主线程退出时才会被调用*/
void stop_assoc_maintenance_thread() {
mutex_lock(&cache_lock);
do_run_maintenance_thread = ;
pthread_cond_signal(&maintenance_cond);
mutex_unlock(&cache_lock); /* Wait for the maintenance thread to stop */
pthread_join(maintenance_tid, NULL);
}
memcached哈希表操作主要逻辑笔记的更多相关文章
- 分布式缓存系统 Memcached 哈希表操作
memcached 中有两张hash 表,一个是“主hash 表”(primary_hashtable),另外一个是“原hash 表”(old_hashtable).一般情况下都在主表中接受操作,在插 ...
- memcached set命令的大致处理逻辑笔记
这次记录状态机的主要逻辑,跟踪set命令的执行流程,暂不涉及到内存申请这一块,下面内容基本都是代码注释 首先还是补充了解下客户连接在发送数据到数据被处理并返回过程中conn的各种状态的表示 enum ...
- 理解Golang哈希表Map的元素
目录 概述 哈希函数 冲突解决 初始化 结构体 字面量 运行时 操作 访问 写入 扩容 删除 总结 在上一节中我们介绍了 数组和切片的实现原理,这一节会介绍 Golang 中的另一个集合元素 - 哈希 ...
- libevent中evmap实现(哈希表)
libevent中,需要将大量的监听事件event进行归类存放,比如一个文件描述符fd可能对应多个监听事件,对大量的事件event采用监听的所采用的数据结构是event_io_map,其实现通过哈希表 ...
- 【Python算法】哈希存储、哈希表、散列表原理
哈希表的定义: 哈希存储的基本思想是以关键字Key为自变量,通过一定的函数关系(散列函数或哈希函数),计算出对应的函数值(哈希地址),以这个值作为数据元素的地址,并将数据元素存入到相应地址的存储单元中 ...
- 第三十四篇 玩转数据结构——哈希表(HashTable)
1.. 整型哈希函数的设计 小范围正整数直接使用 小范围负整数整体进行偏移 大整数,通常做法是"模一个素数" 2.. 浮点型哈希函数的设计 转成整型进行处理 3.. 字符串 ...
- freeswitch APR库哈希表
概述 freeswitch的核心源代码是基于apr库开发的,在不同的系统上有很好的移植性. 哈希表在开发中应用的非常广泛,主要场景是对查询效率要求较高的逻辑,是典型的空间换时间的数据结构实现. 大多数 ...
- [译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并修改其中的Value 学习笔记——异步 程序员常说的「哈希表」是个什么鬼?
[译]聊聊C#中的泛型的使用(新手勿入) 写在前面 今天忙里偷闲在浏览外文的时候看到一篇讲C#中泛型的使用的文章,因此加上本人的理解以及四级没过的英语水平斗胆给大伙进行了翻译,当然在翻译的过程中发 ...
- Java基础知识笔记(一:修饰词、向量、哈希表)
一.Java语言的特点(养成经常查看Java在线帮助文档的习惯) (1)简单性:Java语言是在C和C++计算机语言的基础上进行简化和改进的一种新型计算机语言.它去掉了C和C++最难正确应用的指针和最 ...
随机推荐
- 无法在web服务器下启动调试。该Web服务器未及时响应
下午在运行项目的时候,突然出现了以下错误: 无法在web服务器上启动调试.该Web服务器未及时响应.可能是因为另一个调试器已连接到该Web服务器. 搜索了很久才找到这个解决方案: 1:Web.conf ...
- Python实现在给定整数序列中找到和为100的所有数字组合
摘要: 使用Python在给定整数序列中找到和为100的所有数字组合.可以学习贪婪算法及递归技巧. 难度: 初级 问题 给定一个整数序列,要求将这些整数的和尽可能拼成 100. 比如 [17, 1 ...
- Java SE 基础知识(一)
一.基础知识 1. Java SE : Java Standard Edition Java ME : Java Micro Edition Java EE : Java Enterprise Edi ...
- leetcode 136 Single Number, 260 Single Number III
leetcode 136. Single Number Given an array of integers, every element appears twice except for one. ...
- 函数对象与仿函数(function object and functor)
part 1. 仿函数在STL组件中的关系 如下图: # 仿函数配合算法完成不同的策略变化. # 适配器套接仿函数. part 2. 仿函数介绍 传递给算法的“函数型实参”不一定得是函数,可以是行为类 ...
- Linux系统对IO端口和IO内存的管理
引用:http://blog.csdn.net/ce123_zhouwei/article/details/7204458 一.I/O端口 端口(port)是接口电路中能被CPU直接访问的寄存器的地址 ...
- QT中VideoProbe的简介和实现
一.遇到问题 在Android机上使用QT进行图像处理程序设计的时候,遇到的一个比较明显的问题就是图片采集的问题----摄像头获得是实时的视频,如果我们想从中动态地截获图片,并且转换成M ...
- Spark样本类与模式匹配
一.前言 样本类(case class)与模式匹配(pattern matching)是Scala中一个比较复杂的概念,往往让人感觉深陷泥沼.我在这里对Scala中的样本类与模式匹配进行了一些整理,希 ...
- 04_kafka python客户端_Producer模拟
使用的python库: kafka-python 安装方式: pip install kafka-python 简单的模拟Producer """ Kafka Produ ...
- NOI 4978 宠物小精灵之收服(二维背包)
http://noi.openjudge.cn/ch0206/4978/ 描述 宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事. 一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物 ...