解读Redis dict核心数据结构


  • Redis中最重要的数据结构就是dict,每个redisDb中包含一个指针指向dict。

  • 相关数据结构
    redis.h

?
1
2
#define DICT_OK 0
#define DICT_ERR 1

dictEntry类似于一个node,它保存者key,value,next用于iterator迭代时使用

?
1
2
3
4
5
6
7
8
9
typedef struct dictEntry {
void *key;
     union {
         void *val;
         uint64_t u64;
         int64_t s64;
     } v;
     struct dictEntry *next;
} dictEntry;

每个dict都拥有一个自己dictType,使得每个dict不同,如hash函数不同,键值复制,比较不同等等,使得dict的用途多元化

?
1
2
3
4
5
6
7
8
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;

dictht 是一个哈希表,每个dict中含有两个dictht,多的一个是用于扩容时使用, size指的是table指针数组的大小,这里使用了当hash值相同时,每个table[index]是一个dictEntry*, 使得hash值相同的两个dictEntry能成链表保存,这里的size永远是2的指数方,这样sizemask=size-1,(用于计算到的hash值&sizemask)在size范围内。used就是dictht中dictEntry的数量

?
1
2
3
4
5
6
7
8
/* 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;

dict中rehashidx指示是否在扩容,就是转移ht[0]到ht[1]中。iterators特指safe iterators的数量,safe iterators就是在迭代过程中能增加,删除dictEntry的iterators。而非safe iterators不计入其中,因为这些iterators只能进行next()操作,不影响dict的rehash

?
1
2
3
4
5
6
7
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;

table,index,分别为此时iterator位于的表的位置,nextEntry用于safe iterator进行删除操作时,提前保存下一个entry。

?
1
2
3
4
5
6
7
8
9
/* 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;
  • dict的创建和释放
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/* Create a new hash table */
dict *dictCreate(dictType *type,
void *privDataPtr)
{
     dict *d = zmalloc( sizeof (*d));
 
     _dictInit(d,type,privDataPtr);
     return d;
}
 
/* Reset a hash table already initialized with ht_init().
* NOTE: This function should only be called by ht_destroy(). */
static void _dictReset(dictht *ht)
{
     ht->table = NULL;
     ht->size = 0;
     ht->sizemask = 0;
     ht->used = 0;
}
 
/* Initialize the hash table */
int _dictInit(dict *d, dictType *type,
void *privDataPtr)
{
     _dictReset(&d->ht[0]);
     _dictReset(&d->ht[1]);
     d->type = type;
     d->privdata = privDataPtr;
     d->rehashidx = -1;
     d->iterators = 0;
     return DICT_OK;
}
 
/* Clear & Release the hash table */
void dictRelease(dict *d)
{
     _dictClear(d,&d->ht[0]);
     _dictClear(d,&d->ht[1]);
     zfree(d);
}
 
/* Destroy an entire dictionary */
int _dictClear(dict *d, dictht *ht)
{
     unsigned long i;
 
/* Free all the elements */
     for (i = 0; i < ht->size && ht->used > 0; i++) {
         dictEntry *he, *nextHe;
 
         if ((he = ht->table[i]) == NULL) continue ;
         while (he) {
             nextHe = he->next;
             dictFreeKey(d, he);
             dictFreeVal(d, he);
             zfree(he);
             ht->used--;
             he = nextHe;
         }
     }
/* Free the table and the allocated cache structure */
     zfree(ht->table);
/* Re-initialize the table */
     _dictReset(ht);
     return DICT_OK; /* never fails */
}

创建和释放dict结构没有不同之处,简单的初始化dict结构,将两个dictht都设为NULL,在增加dictEntry时才会初始化dictht表结构。

  • dict扩容
    dict扩容是redis实现dict结构最重要也是最复杂的操作。我们用Q&A的方式来解释整个扩容过程。
  1. 何时需要扩容?
    每次增加dictEntry时,都会调用一次_dictExpandIfNeeded()这个函数。
    1.如果dict正在扩容,则返回正常DICT_OK
    2.如果dict刚刚初始化,也就是两个dictht都为NULL,则调用dictExpand()函数初始化。dictExpand()同样会做一次判断两个dictht都为NULL,然后直接赋给ht[0]。
    3.如果可以扩容(dict_can_resize=1),那么只要现在表中键总数大于表的长度就开始扩容。如果不能扩容(也就是dict_can_resize=0),但是如果表中键总数与表的长度的比值大于某一个阀值(由dict_force_resize_ratio静态变量决定),那么就强制扩容。

  2. 扩容是怎么进行的?
    之前所说的每个dict中都有两个哈希表结构dictht *ht[2]。当开始扩容时,把第一个ht作为原始表,第二个作为扩容后的表。dict中rehashidx决定了目前扩容的进度。

  3. 扩容过程什么时候执行?
    当决定开始扩容后,dict中rehashidx赋为0,然后有两个函数来进行扩容。

?
1
2
int dictRehash(dict *d, int n);
int dictRehashMilliseconds(dict *d, int ms);

第一个函数中n决定每次rehash(也就是转移dictEntry)的次数,第二个函数决定调用rehash函数的时间。
每次执行查找,增加,删除操作都会执行一次dictRehash(1)(如果正在扩容),使得整个漫长的扩容过程隐含在每一次简单的操作中。

  • dict的hash函数
    dict中hash函数有一个通用函数:
?
1
2
3
4
5
6
7
unsigned int dictGenHashFunction( const unsigned char *buf, int len) {
unsigned int hash = dict_hash_function_seed;
 
     while (len--)
     hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
     return hash;
}

针对整型又有另外一个:

?
1
2
3
4
5
6
7
8
9
10
unsigned int dictIntHashFunction(unsigned int key)
{
     key += ~(key << 15);
     key ^= (key >> 10);
     key += (key << 3);
     key ^= (key >> 6);
     key += ~(key << 11);
     key ^= (key >> 16);
     return key;
}

对hash函数没有太多了解,因此也无法判断这个函数的效果相比其他如何和优点。

  • dict的增加,替代,删除,查找键值
    查找键值:
?
1
2
dictEntry * dictFind(dict *d, const void *key);
void *dictFetchValue(dict *d, const void *key);

dictFind根据key返回相应的dictEntry,而dictFetchValue()返回相应的值。

增加键值:

?
1
2
int dictAdd(dict *d, void *key, void *val);
dictEntry *dictAddRaw(dict *d, void *key);

dictAddRaw()属于较低级的函数,它暴露了dictEntry给用户,用户可以根据返回dictEntry进行额外的操作,它调用_dictKeyIndex()得到key哈希后的值,也就是dictht中table指针数组的下标,如果该key已经存在返回-1(值得注意的是_dictKeyIndex()只用于在dictAddRaw()中调用,而是否扩容判断就在_dictKeyIndex()中,因此,只有增加dictEntry操作才会判断是否扩容)。dictAdd()是dictAddRaw的包装函数,直接调用了dictAddRaw,并且对这个dictEntry进行值的赋予。
另外值得注意的是增加操作如果在扩容时期时,会直接添加到扩容后的表。

替换键值:

?
1
2
int dictReplace(dict *d, void *key, void *val);
dictEntry *dictReplaceRaw(dict *d, void *key);

dictReplaceRaw()也是dictAddRaw的包装函数,只不过它会调用dictFind()首先查找是否有这个键,如果没有直接调用dictAddRaw(),如果有,那么返回存在的dictEntry。dictReplace()会首先尝试调用dictAdd(),如果添加失败,那么再调用dictFind()得到存在的dictEntry,再对值进行覆盖。这么考虑是因为往往替代操作很大一部分key在表中是不存在的,直接添加免去了查找。节约了运行时间。

删除键值:

?
1
2
int dictDelete(dict *d, const void *key);
int dictDeleteNoFree(dict *d, const void *key);

两个函数都是静态函数
static int dictGenericDelete(dict *d, const void *key, int nofree)
的包装函数,只是在是否释放掉键值上不同。
dictGenericDelete()跟dictAddRaw()类似,都是尝试得到对应key的dictEntry,如果有,则删除。

  • 迭代器操作
?
1
2
3
4
dictIterator *dictGetIterator(dict *d);
dictIterator *dictGetSafeIterator(dict *d);
dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter);

dictGetIterator()初始化了dictIterator,dictGetSafeIterator()包装了dictGetIterator(),只是将safe属性赋为1。
dictNext()参数会判断iterator是否safe,如果safe,那么计入dict结构中的iterators。这个iterators决定是否在增加,删除,查找过程中调用rehash过程。

转自:http://www.wzxue.com/%E8%A7%A3%E8%AF%BBredis-dict%E6%A0%B8%E5%BF%83%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值