Redis的字典的底层实现是用哈希表。
/* 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;
table是一个数组指针,每一个数组成员是一个dictEntry*,每一个dictEntry都是一个键值对,size是table的大小,sizemask=size-1,大小掩码,而used是哈希表总共有的键值对。因为哈希表对于简直哈希值的冲突解决是链地址法,每一个dictEntry都有一个next指针,用来实现链表。
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;
一般的字典会有两个哈希表,目的是为了进行rehash,type用于制定一些特殊的操作,比如哈希函数,比较函数等。
不同用途的对象,type不尽相同,dictCreate时传入了指定的dictType,比如集合setDictType。
随着操作的不断执行,为了让哈希表的负载因子维持在合理的范围内,Redis会自动对哈希表进行扩展和收缩,之前为啥有两个哈希表,就是用来rehash用的。
主要步骤如下:
1. 为ht[1]分配足够的空间,一般都是2的n次方>=ht[0].used
2. 把ht[0]上的键值对映射到ht[1]的哈希表;
3. 交换两个哈希表,释放原来的ht[0]。
值得注意的是,哈希表的负载因子会和BGSAVE和BGREWRITEAOF有关。不在这些操作是,负载因子负载因子大于等于1,否则负载因子大于等于5,负载因子used/size.另一方面,负载因子小于0.1也会执行rehash。
一般情况下,字典的rehash不是一次性做完的,而是在每次主循环tick的时候,rehash部分key到ht[1],所以又一个rehashidx用于记录当前rehash的进度;而rehash期间,对字典做的操作会在两个哈希表中操作。
删改查会同时在两个哈希表中执行,增操作会在ht[1]中进行。