介绍
dict(字典)是redis中的一种数据结构,也是key-value的基础,下面介绍dict的实现原理。
数据结构
先上图
dictEntry
对于初学者来说,这张图比较难理解,dict的结构也确实比sds要更加复杂,那么下面我们就从最简单的开始讲起。
能看到这篇文章的同学,应该都已经知道了,redis是一个key-value数据库,一个key只对应一个value
例如:
- key_1 -> value_1
- key_2 -> value_2
那么一个key-value对就是一个Entry,在redis中叫作dictEntry(上图中标绿的),首先来看下dictEntry的定义
typedef struct dictEntry {
void *key; // key
union {
void *val;// value
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; // 下一个节点
} dictEntry;
key和value都存储在dictEntry中
dictht
那么问题来了,redis中可以存储大量的dictEntry,这些dictEntry是如何被组织起来的,以及如何迅速查找到的?
redis中dictiht的数据结构(上图标黄的部分)的字段table指向了一个dictEntry数组(数组的初始大小为4),而dictEntry就会按照一定的规则被存放在table中。
例如,dictEntry(key_1 -> value_1)要存放到table中,redis首先会对"key_1"做哈希运算(这里不详细讨论),最终得到一个值n,(介于0~size-1之间),这个dictEntry将会被存放到table[n]的位置,反之,查询key_1时,也会做相同的运算,到对应的位置去获取key_1的值。
然而这里有一个问题,不同的key做完哈希后,有可能出现哈希值相同的情况,因此可能出现冲突的情况。redis的解决方式是,将哈希值相同的key所在的dictEntry用指针的方式链接起来。
- dictht定义
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
看起来dictht已经实现了存储key-value的所有功能,那为什么还需要dict呢?
dict的出现,主要是为了解决dictiht扩容的问题,为什么dictiht需要扩容?原因在于,当key的数量达到一定程度时,相同哈希值得key会越来越多,链接也会越来越长,效率也会越来越低。
dict
dict的出现,正是为了解决dictiht的扩容问题,简单地说,当dictiht需要扩容时,会先创建一个2倍大小的新的dictiht,然后逐渐将数据迁移过来,迁移完成后将老的dictiht释放。
这样一来,dictEntry,dictht和dict的关系就理清了,接下来看一些细节的东西。
- dict定义
typedef struct dict {
dictType *type; // 特定于类型的处理函数
void *privdata; // 类型处理函数的私有数据
dictht ht[2]; // 2个哈希表
long rehashidx; // 记录rehash状态的标志,值为-1表示rehash未进行
int iterators; // 正在运作的迭代器数量(这里其实指的是安全迭代器的数量,安全迭代器在迭代的时候不允许rehash)
} dict;
rehash
dict的扩容操作,也叫做rehash。dict中存在两个dictht,dictht[0]和dictht[1],dictht[0]用来存储数据,dictht[1]只有在做rehash的过程中才会使用到。
dictht中存在一个指标,负载因子(used/size)当满足一下两个情况之一,需要执行rehash
- 当负载因子>1,并且没有在做rdb dump和aof rewrite操作
- 如果负载因子>5
相关代码
static int dict_can_resize = 1; //当
static unsigned int dict_force_resize_ratio = 5;
static int _dictExpandIfNeeded(dict *d)
{
if (dictIsRehashing(d)) return DICT_OK; // 字典正在rehash,直接返回
if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE); // 如果字典的大小为0,则将大小设置为初始值4
// 当负载因子达到1时,就可以扩容了,不过字典还要处于可以扩容状态,如果处于不可扩容状态的话,负载因子达到5也是可以强制扩容的
if (d->ht[0].used >= d->ht