Redis数据结构之字典-dict
dict
是Redis
服务器中出现最为频繁的复合型数据结构,除hash
使用dict
之外,整个Redis
数据库中所有的key
和value
也会组成一个全局字典,还有带过期时间的key
集合也是一个字典。
zset
集合中存储value
和score
的映射关系也是通过dict
结构实现的。
结构
// 哈希表 typedef struct dictht { dictEntry **table; // 哈希表数组,二维 long size; // 哈希表大小 long used; // 哈希表已有节点数 } dictht; // 哈希表节点 typedef struct dictEntry { void *key; // 键 void *val; // 值 dictEntry *next; // 指向下一个哈希表节点,形成链表 } dictEntry;
内部是二维数组
dict
内部是一个二维数组,包含两个hashtable
。
通常情况下只有一个hashtable
是有值的,但是在扩容、缩容时,需要分配新的hashtable
,然后进行渐进式rehash
,此时两个hashtable
分别是旧的hashtable
和新的hashtable
。在rehash
结束后,旧的hashtable
被删除,新的hashtable
取而代之。
使用链表法解决哈希冲突
从哈希表节点结构dictEntry
中可以看到,每一个节点都有一个指向下一个dictEntry
的指针,说明Redis
中主要通过使用链表法解决哈希冲突,即每一个hashtable
中存储的是一个链表,表中存储指向链表头部元素的指针。
rehash
过程
1.为字典分配空间
假设字典中两个hashtable
分别为h[0]
和h[1]
,数据存储在h[0]
中。
在执行rehash
之前,需要为h[1]
分配空间,这个hashtable
的大小取决于需要执行的操作和当前h[0]
包含键值对数量即h[0].used
:
-
扩展操作:
h[1]
大小为第一个大于等于h[0].used*2
的2
的n
次方幂 -
收缩操作:
h[1]
大小为第一个大于等于h[0].used
的2
的n
次方幂
2.执行rehash
将保存在h[0]
中的键值对rehash
到h[1]
上,rehash
指重新计算键的hash
值和索引值,然后将键值对放置在h[1]
的指定索引位置上
3.释放空间
所有键值对迁移完成之后,h[0]
变成空表,此时释放h[0]
,然后将h[1]
设置为h[0]
,最后在h[1]
位置上新创建一个hashtable
,为下一次rehash
做准备
渐进式rehash
在进行rehash
时,需要申请新数组,然后迁移所有键值对,这是一个时间复杂度为O(n)
的操作,所以对于大字典的扩容需要耗费一定时间。
为避免阻塞,Redis
使用渐进式rehash
,多次、渐进地完成迁移,以避免集中式rehash
带来的庞大计算量。
扩展、收缩时机
负载因子 = 已保存节点 / 哈希表大小即load_factor = ht[0].used / h[0].size
-
没有执行
BGSAVE/BGREWRITEAOF
时,负载因子大于等于1
-
执行
BGSAVE/BGREWRITEAOF
时,负载因子大于等于5
收缩时机
-
负载因子小于