redis是一个键值型数据库,而键与值的映射关系正是通过Dict来实现的
Dict由三部分组成,分别是哈希表(DictHashTable),哈希节点(DictEntry)与字典(Dict)
整体Dict结构如下:
Dict中的 HashTable就是数组结合单链表实现的(默认容量为4),当集合中元素较多时,必然导致哈希冲突,链表过长,则查询效率会大大降低。因此Dict在每次新增键值对时都会检查负载因子
(LoadFactor = used / size),当满足下列条件时会进行扩容
1.哈希表的LoadFactor >= 1,并且服务器没有执行BGSAVE或者BGREWRITEAOF等后台进程
2.哈希表的LoadFactor >= 5
扩容后的大小为第一个大于等于 used+1 的2^n
每次删除元素成功时,也会对负载因子进行检查,当LoadFactor < 0.1时,会做哈希表收缩
不管是进行扩容还是进行收缩,都必定会创建新的哈希表,导致哈希表的size和sizemask变化,而key的查询与sizemask有关,因此必须对哈希表中的每一个key重新计算索引,插入新的哈希表,这个过程称为rehash。rehash过程如下:
①
计算新
hash
表的
realeSize
,值取决于当前要做的是扩容还是收缩:
如果是扩容,则新
size
为第一个大于等于
dict.ht[0].used + 1
的2^n
如果是收缩,则新
size
为第一个大于等于
dict.ht[0].used
的2^n
(不得小于
4
)
②
按照新的
realeSize
申请内存空间,创建
dictht
,设置dictht中的值后赋值给
dict.ht[1]
③
设置
dict.rehashidx
= 0
,标示开始
rehash
④
将
dict.ht[0]
中的每一个
dictEntry
都
rehash
到dict.ht[1]
④
每次执行新增、查询、修改、删除操作时,都检查一下
dict.rehashidx
是否大于
-1
,如果是则将
dict.ht[0].table[
rehashidx
]
的
entry
链表
rehash
到
dict.ht[1]
,并且将
rehashidx
++
。直至
dict.ht[0]
的所有数据都
rehash
到
dict.ht[1]
⑤
将
dict.ht[1]
赋值给
dict.ht[0]
,给
dict.ht[1]
初始化为空哈希表,释放原来的
dict.ht[0]
的内存
⑥
将
rehashidx
赋值为
-1
,代表
rehash
结束
⑦
在
rehash
过程中,新增操作,则直接写入
ht
[1]
,查询、修改和删除则会在
dict.ht[0]
和
dict.ht[1]
依次查找并执行。这样可以确保
ht
[0]
的数据只减不增,随着
rehash
最终为空
总结:
1.Dict中包含了两个哈希表,平常用ht[0],ht[1]用来rehash
2.底层是数组加链表
Dict的伸缩:
1.当LoadFactor > 1并且没有子进程任务或LoadFactor > 5,Dict扩容
2.当LoadFactor < 0.1 时,Dict收缩
3.扩容大小第一个大于等于 used+1 的2^n
4.收缩大小为第一个大于等于 used+1 的2^n
5.Dict采用渐进式的rehash,每次访问Dict时进行一次rehash,将数组一个角标下的元素(可能是一个链表)复制到 ht[1] 中
6.rehash时 ht[0] 只减不增,新增操作只在ht[1]中进行,其他操作在两个哈希表中都有可能进行