来自http://yulinlu.blog.163.com/blog/static/58815698201162361419296/
相关文件:
Dict.c
关键数据结构如下
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
int rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;
一个redis哈希表对应一个dict,dict里面有两个dictht(ht),每个dictht就是一个标准的哈希表。之所以要两个dictht是因为redis在发现哈希表需要扩容的时候,不是采取简单的直接分配新空间然后把内容复制过去的方法,它扩容的时候是分配一个新的哈希表ht[1],然后在往后的每次查询和更新的时候都把ht[0]的一个节点移动到ht[1],在扩容结束后ht[0]的内容为空,会被销毁然后把ht[1]覆盖到ht[0],ht[1]则初始化为初始值等待下一次扩容。
type存储了这个dict的算子,iterators表示这个哈希表当前正在使用的安全迭代器有多少个。rehashidx表示rehash到ht[0]的哪一个index,-1表示不需要rehash。redis的哈希表里面每次扩容就要rehash,rehash的操作就是把ht[0]的东西一点点放到ht[1]里面(见dictRehash)
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
dictht 中,table是主体内容,每个节点是一个dictEntry链表表头。size是table的大小,sizemask是size-1,用来计算求模的时候用的,used是哈希表里面已经使用了的大小。
typedef struct dictEntry {
void *key;
void *val;
struct dictEntry *next;
} dictEntry;
dictEntry链表是哈希表里面解决冲突的方式,两个不同的key如果经过hash后得到同一个index,他们就会存储在这个index对应的链表里,经典处理方式。
typedef struct dictType {
unsigned int (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType;
dict里面有一个dictType类型的type,其实就是让用户能自定义一大堆的算子而已
/* If safe is set to 1 this is a safe iteartor, that means, you can call
* dictAdd, dictFind, and other functions against the dictionary even while
* iterating. Otherwise it is a non safe iterator, and only dictNext()
* should be called while iterating. */
typedef struct dictIterator {
dict *d;
int table, index, safe;
dictEntry *entry, *nextEntry;
} dictIterator;
dictIterator 是哈希表的迭代器,table是dict 里面ht的索引,index是dictht 里面table的索引,entry是迭代器当前的dictEntry ,nextEntry是entry->next。比较特别的是safe,dictIterator 分为安全迭代器和非安全迭代器,非安全的迭代器只能在dictNext里用,安全的迭代器则可以在所有接口中使用。之所以有这个区别,还是因为redis的哈希表的逐步扩容的机制。如果产生了一个安全迭代器,dict的iterators就会加1,如果iterators不等于0这个哈希表不能被Rehash。这表示,如果这个哈希表正在扩容,他将停止从ht[0]挪动数据到ht[1]。
其实除去rehash外,其他东西都是标准的hash表,rehash看dictRehash就可以了
有问题的地方:
dictGetRandomKey极端情况可能会死循环(至少会卡CPU)
if (dictIsRehashing(d)) {
do {
h = random() % (d->ht[0].size+d->ht[1].size);
he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :
d->ht[0].table[h];
} while(he == NULL);
} else {
do {
h = random() & d->ht[0].sizemask;
he = d->ht[0].table[h];
} while(he == NULL);
}
这一段,没有设置上限,如果random一直没命中,就会一直尝试下去