redis是一个键值型数据库,而键与值的映射关系正是通过Dict来实现的

Dict由三部分组成,分别是哈希表(DictHashTable),哈希节点(DictEntry)与字典(Dict)

redis Hash 可以存过期时间嘛 redis hash不能存vector_redis Hash 可以存过期时间嘛

redis Hash 可以存过期时间嘛 redis hash不能存vector_链表_02

 

redis Hash 可以存过期时间嘛 redis hash不能存vector_链表_03

整体Dict结构如下: 

redis Hash 可以存过期时间嘛 redis hash不能存vector_链表_04

 

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]中进行,其他操作在两个哈希表中都有可能进行