介绍
Redis的哈希字典通过key值来找对应的value。需要注意的是Redis的字典是如何进行rehash的。
源码
dict.h dict.c
数据结构
如上图所示,哈希字典用dict结构体表示,其中含有两个哈希表,主要用于进行rehash操作。同时哈希表使用量表的方式解决冲突。具体的数据结构如下:
- /*
- * 哈希表节点
- */
- typedef struct dictEntry {
- // 键
- void *key;
- // 值
- union {
- void *val;
- uint64_t u64;
- int64_t s64;
- } v;
- // 链往后继节点
- struct dictEntry *next;
- } dictEntry;
- /*
- * 特定于类型的一簇处理函数
- */
- 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;
- /*
- * 哈希表
- */
- typedef struct dictht {
- // 哈希表节点指针数组(俗称桶,bucket)
- dictEntry **table;
- // 指针数组的大小
- unsigned long size;
- // 指针数组的长度掩码,用于计算索引值
- unsigned long sizemask;
- // 哈希表现有的节点数量
- unsigned long used;
- } dictht;
- /*
- * 字典
- *
- * 每个字典使用两个哈希表,用于实现渐进式 rehash
- */
- typedef struct dict {
- // 特定于类型的处理函数
- dictType *type;
- // 类型处理函数的私有数据
- void *privdata;
- // 哈希表(2个)
- dictht ht[2];
- // 记录 rehash 进度的标志,值为-1 表示 rehash 未进行
- int rehashidx;
- // 当前正在运作的安全迭代器数量
- int iterators;
- } dict;
- /*
- * 字典迭代器
- *
- * 如果 safe 属性的值为 1 ,那么表示这个迭代器是一个安全迭代器。
- * 当安全迭代器正在迭代一个字典时,该字典仍然可以调用 dictAdd 、 dictFind 和其他函数。
- *
- * 如果 safe 属性的值为 0 ,那么表示这不是一个安全迭代器。
- * 如果正在运作的迭代器是不安全迭代器,那么它只可以对字典调用 dictNext 函数。
- */
- typedef struct dictIterator {
- // 正在迭代的字典
- dict *d;
- int table, // 正在迭代的哈希表的号码(0 或者 1)
- index, // 正在迭代的哈希表数组的索引
- safe; // 是否安全?
- dictEntry *entry, // 当前哈希节点
- *nextEntry; // 当前哈希节点的后继节点
- } dictIterator;
分析
rehash
在字典中使用了两个hash表就是为了方便进行rehash的,rehash主要有两种方式,一是由后台调用cron进行按照100步进进行hash,二是在进行添加删除字典中的元素的时候进行1步的hash。
这里每一次的hash的步进是以真个hash key对应的链表为单位的,也就是说1步进的rehash是将原来hash表中的一个key链表进行rehash到新的哈希表中的位置。这里可以看出Redis在此处是牺牲了内存但是换来了性能的响应。将rehash进行分散操作可以避免阻塞导致的性能急剧下降。
迭代器
迭代器的属性如果是安全的则表明可以在迭代的过程中进行dictAdd等其他的操作,而如果迭代器是不安全的则只能进行next的操作。
同时Redis限制在有迭代器访问字典的时候进行rehash,因为这样会造成对一个元素访问多次。但是在rehash的过程中可以随时的对字典进行遍历,一旦迭代器开始遍历字典了,rehash就会暂停知道没有迭代器在对字典进行遍历为止。