memcached源码分析-----哈希表基本操作以及扩容过程
来源:程序员人生 发布时间:2015-01-20 08:42:39 阅读次数:4206次
转载请注明出处:http://blog.csdn.net/luotuo44/article/details/42773231
温馨提示:本文用到了1些可以在启动memcached设置的全局变量。关于这些全局变量的含义可以参考《memcached启动参数详解》。对这些全局变量,处理方式就像《如何浏览memcached源代码》所说的那样直接取其默许值。
assoc.c文件里面的代码是构造1个哈希表。memcached快的1个缘由是使用了哈希表。现在就来看1下memcached是怎样使用哈希表的。
哈希结构:
main函数会调用assoc_init函数申请并初始化哈希表。为了减少哈希表产生冲突的可能性,memcached的哈希表是比较长的,并且哈希表的长度为2的幂。全局变量hashpower用来记录2的幂次。main函数调用assoc_init函数时使用全局变量settings.hashpower_init作为参数,用于指明哈希表初始化时的幂次。settings.hashpower_init可以在启动memcached的时候设置,具体可以参考《memcached启动参数详解和关键配置的默许值》。
//memcached.h文件
#define HASHPOWER_DEFAULT 16
//assoc.h文件
unsigned int hashpower = HASHPOWER_DEFAULT;
#define hashsize(n) ((ub4)1<<(n))//这里是1 左移 n次
//hashsize(n)为2的幂,所以hashmask的值的2进制情势就是后面全为1的数。这就很像位操作里面的 &
//value & hashmask(n)的结果肯定是比hashsize(n)小的1个数字.即结果在hash表里面
//hashmask(n)也能够称为哈希掩码
#define hashmask(n) (hashsize(n)⑴)
//哈希表数组指针
static item** primary_hashtable = 0;
//默许参数值为0。本函数由main函数调用,参数的默许值为0
void assoc_init(const int hashtable_init) {
if (hashtable_init) {
hashpower = hashtable_init;
}
//由于哈希表会渐渐增大,所以要使用动态内存分配。哈希表存储的数据是1个
//指针,这样更省空间。
//hashsize(hashpower)就是哈希表的长度了
primary_hashtable = calloc(hashsize(hashpower), sizeof(void *));
if (! primary_hashtable) {
fprintf(stderr, "Failed to init hashtable.
");
exit(EXIT_FAILURE);//哈希表是memcached工作的基础,如果失败只能退出运行
}
}
说到哈希表,那末就对应有两个问题,1个是哈希算法,1个怎样解决冲突。
对哈希函数(算法),memcached直接使用开源的MurmurHash3和jenkins_hash两个中的1个。默许是使用jenkins,可以在启动memcached的时候设置设置为MurmurHash3。memcached是直接把客户端输入的键值作为哈希算法的输入,得到1个32位的无符号整型输出(用变量hv存储)。由于哈希表的长度没有2^32- 1这么大,所以需要用到前面代码中的hashmask宏进行截断。由因而位操作,所以常常能在memcached代码中看的hv & hashmask(hashpower)。
memcached使用最多见的链地址法解决冲突问题。从前面的代码可以看到,primary_hashtable是1个的2级指针变量,它指向的是1个1维指针数组,数组的每个元素指向1条链表(链表上的item节点具有相同的哈希值)。数组的每个元素,在memcached里面也称为桶(bucket),所以后文的表述中会使用桶。下图是1个哈希表,其中第0号桶有2个item,第2、3、5号桶各有1个item。item就是用来存储用户数据的结构体。
基本操作:
插入item:
接着看1下怎样在哈希表中插入1个item。它是直接根据哈希值找到哈希表中的位置(即找到对应的桶),然后使用头插法插入到桶的冲突链中。item结构体有1个专门的h_next指针成员变量用于连接哈希冲突链。
static unsigned int hash_items = 0;//hash表中item的个数
/* Note: this isn't an assoc_update. The key must not already exist to call this */
//hv是这个item键值的哈希值
int assoc_insert(item *it, const uint32_t hv) {
unsigned int oldbucket;
//使用头插法 插入1个item
//第1次看本函数,直接看else部份
if (expanding &&
(oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
{
...
} else {
//使用头插法插入哈希表中
it->h_next = primary_hashtable[hv & hashmask(hashpower)];
primary_hashtable[hv & hashmask(hashpower)] = it;
}
hash_items++;//哈希表的item数量加1
…
return 1;
}
查找item:
往哈希表插入item后,就能够开始查找item了。下面看1下怎样在哈希表中查找1个item。item的键值hv只能定位到哈希表中的桶位置,但1个桶的冲突链上可能有多个item,所以除查找的时候除需要hv外还需要item的键值。
//由于哈希值只能肯定是在哈希表中的哪一个桶(bucket),但1个桶里面是有1条冲突链的
//此时需要用到具体的键值遍历并逐一比较冲突链上的所有节点。虽然key是以'