Quantcast
Channel: 董的博客 » NoSQL数据库
Viewing all articles
Browse latest Browse all 9

Redis源码研究—哈希表

$
0
0


重大消息:我的Hadoop新书《Hadoop技术内幕:深入解析MapReduce架构设计与实现原理》已经开始在各大网站销售了,购书链接地址: 当当购书网址京东购书网址卓越购书网址。新书官方宣传主页: http://hadoop123.com/

1. Redis中的哈希表

前面提到Redis是个key/value存储系统,学过数据结构的人都知道,key/value最简单的数据结果就是哈希表(当然,还有其他方式,如B-树,二叉平衡树等),hash表的性能取决于两个因素:hash表的大小和解决冲突的方法。这两个是矛盾的:hash表大,则冲突少,但是用内存过大;而hash表小,则内存使用少,但冲突多,性能低。一个好的hash表会权衡这两个因素,使内存使用量和性能均尽可能低。在Redis中,哈希表是所有其他数据结构的基础,对于其他所有数据结构,如:string,set,sortedset,均是保存到hash表中的value中的,这个可以很容易的通过设置value的类型为void*做到。本文详细介绍了Redis中hash表的设计思想和实现方法。

【注】 本文的源代码分析是基于redis-2.4.3版本的。

2. Redis哈希表的设计思想

下图是从淘宝《Redis内存存储结构分析》中摘得的图片,主要描述Redis中hash表的组织方式。

在Redis中,hash表被称为字典(dictionary),采用了典型的链式解决冲突方法,即:当有多个key/value的key的映射值(每对key/value保存之前,会先通过类似HASH(key) MOD N的方法计算一个值,以便确定其对应的hash table的位置)相同时,会将这些value以单链表的形式保存;同时为了控制哈希表所占内存大小,redis采用了双哈希表(ht[2])结构,并逐步扩大哈希表容量(桶的大小)的策略,即:刚开始,哈希表ht[0]的桶大小为4,哈希表ht[1]的桶大小为0,待冲突严重(redis有一定的判断条件)后,ht[1]中桶的大小增为ht[0]的两倍,并逐步(注意这个词:”逐步”)将哈希表ht[0]中元素迁移(称为“再次Hash”)到ht[1],待ht[0]中所有元素全部迁移到ht[1]后,再将ht[1]交给ht[0](这里仅仅是C语言地址交换),之后重复上面的过程。

3. Redis哈希表实现

3.1  基本数据结构

Redis哈希表的实现位于文件dict.h和dict.c中,主要数据结构如下:


//hash表结构

typedef struct dictht {

  dictEntry **table; //hash 表中的数据,以key/value形式,通过单链表保存

  unsigned long size; //桶个数

  unsigned long sizemask; //size-1,方便定位

  unsigned long used; //实际保存的元素数

} dictht;

//hash表结构,含有两个hash表,以实现增量再hash算法。

typedef struct dict {

  dictType *type; //hash表的类型,可以是string, list等

  void *privdata; //该hash表的一些private数据

  dictht ht[2];

  int rehashidx; /* rehashing not in progress if rehashidx == -1 */

  int iterators; /* number of iterators currently running */

} dict;

//hash表中每一项key/value,若key的映射值,以单链表的形式保存

typedef struct dictEntry {

  void *key;

  void *val;

  struct dictEntry *next;

} dictEntry;

//每种hash table的类型,里面既有成员函数,又有成员变量,完全是模拟的C++类,注意,每个函数带有的privdata均为预留参数

typedef struct dictType {

  unsigned int (*hashFunction)(const void *key); //要采用的hash函数

  void *(*keyDup)(void *privdata, const void *key); //对key进行拷贝

  void *(*valDup)(void *privdata, const void *obj); //对value进行拷贝

  int (*keyCompare)(void *privdata, const void *key1, const void *key2);//key比较器

  void (*keyDestructor)(void *privdata, void *key);//销毁key,一般为释放空间

  void (*valDestructor)(void *privdata, void *obj);//销毁value,一般为释放空间

} dictType;

3.2  基本操作

Redis中hash table主要有以下几个对外提供的接口:dictCreate、dictAdd、dictReplace、dictDelete、dictFind、dictEmpty等,而这些接口调用了一些基础操作,包括:_dictRehashStep,_dictKeyIndex等。下面分析一下_dictRehashStep函数:

该函数主要完成rehash操作。Hash Table在一定情况下会触发rehash操作,即:将第一个hash table中的数据逐步转移到第二个hash table中。

【1】触发条件


//dict.c, _dictExpandIfNeeded()

if (d->ht[0].used >= d->ht[0].size &&

  (dict_can_resize ||

    d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))

{

  return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ?

    d->ht[0].size : d->ht[0].used)*2);

}

当第一个表的元素数目大于桶数目且元素数目与桶数目比值大于5时,hash 表就会扩张,扩大后新表的大小为旧表的2倍。

【2】转移策略

为了避免一次性转移带来的开销,Redis采用了平摊开销的策略,即:将转移代价平摊到每个基本操作中,如:dictAdd、dictReplace、dictFind中,每执行一次这些基本操作会触发一个桶中元素的迁移操作。在此,有读者可能会问,如果这样的话,如果旧hash table非常大,什么时候才能迁移完。为了提高前移速度,Redis有一个周期性任务serverCron,每隔一段时间会迁移100个桶。


//redis.c

int dictRehashMilliseconds(dict *d, int ms) {

  long long start = timeInMilliseconds();

  int rehashes = 0;

  while(dictRehash(d,100)) {

    rehashes += 100;

    if (timeInMilliseconds()-start > ms) break;

  }

  return rehashes;

}

下面分析一下dictAdd函数:

首先,检查hash table是否正在rehash操作,如果是,则分摊一个rehash开销:

if (dictIsRehashing(d)) _dictRehashStep(d);

然后,检查该key/value的key是否已经存在,如果存在,则直接返回:

if ((index = _dictKeyIndex(d, key)) == -1)

  return DICT_ERR;

需要注意的是,决定是否需要进行rehash是在查找操作(_dictKeyIndex)中顺便做的:


//_dictKeyIndex()

if (_dictExpandIfNeeded(d) == DICT_ERR)

  return -1;

接着,会通过hash算法定位该key的位置,并创建一个dictEntry节点,插入到对应单链表中:


entry = zmalloc(sizeof(*entry));

entry->next = ht->table[index];

ht->table[index] = entry;

ht->used++;

最后将key/value对填充到该entry中:


dictSetHashKey(d, entry, key);

dictSetHashVal(d, entry, val);

这就是整个dictAdd函数的流程。其他操作类似,均是刚开始分摊rehash开销(如果需要),然后通过hash方法定位位置,并进行相应的逻辑操作。

原创文章,转载请注明: 转载自董的博客

本文链接地址: http://dongxicheng.org/nosql/redis-code-hashtable/

作者:Dong,作者介绍:http://dongxicheng.org/about/

本博客的文章集合:


Copyright © 2013
This feed is for personal, non-commercial use only.
The use of this feed on other websites breaches copyright. If this content is not in your news reader, it makes the page you are viewing an infringement of the copyright. (Digital Fingerprint:
)

Viewing all articles
Browse latest Browse all 9

Latest Images

Trending Articles





Latest Images