详解Redis对象底层数据结构 — 字典

三、字典

字典是一种用于保存键值对的抽象数据结构,在很多方面应用十分广泛。字典中每一个键都是独一无二的,这样才能保证每一个键唯一对应着一个值,但是c语言中并没有内置这样的数据结构,所以redis自己实现了一个高效的字典数据结构。在redis中当哈希键包含的元素数量比较多,或者键值对元素都是些比较长的字符串时,redis底层会采用字段作为哈希键的底层实现。

哈希表的实现

包含一个哈希表数组、哈希表大小size、哈希表掩码sizemask(用来决定一个键应该放在哈希表的哪个索引上面)、已使用节点数量used,图如下

详解Redis对象底层数据结构 — 字典_第1张图片

 

 哈希表节点的实现

包含一个键、值(指针、uint64_t u64、int64_t s64 任选一种)、指向下一个节点的指针(拉链法解决哈希冲突),如下图

详解Redis对象底层数据结构 — 字典_第2张图片

 字典的实现

​​​​​​​包含一个type指针(指向一簇用于操作特定类型键值对的函数)、private指针(保存需要传给函数的特定参数)、哈希表数组(两个)、rehash索引(trehashidx 没有进行rehash时值为 -1),如下图

详解Redis对象底层数据结构 — 字典_第3张图片

 哈希算法

当要将一个键值对添加到字典当中去的时候,程序需要先根据键值对的键进行哈希计算,算出哈希值和索引值(哈希值和sizemask相与),然后根据索引值将节点放到指定的位置上。目前常采用MurmurHash2作为字典的哈希算法,优点:即使输入的键是有规律的,算法任然能给出比较好的随机性。

解决哈希冲突

​​​​​​​当有两个或者两个以上的键被分配在一个索引的位置叫做发生了冲突,在Reid's中解决这种哈希冲突采用拉链法解决,剧名思议就是将冲突的节点挂在后面像一个长长的拉链一样(每个节点都有一个指向下一个节点的指针)。因为字典结构中没有一个指向链表表尾的指针,所以为了效率redis采用了头插法来进行插入。

Rehash

当一个索引值上面会挂着长长的一串节点是,势必会影响字典的索引效率,所以当满足一定的条件时,redis会对字典进行rehash

Rehash条件:

服务器目前没有执行bgsave命令或者bgrewriteaof命令时,并且哈希表的负载因子大于1。

服务器目前正在执行bgsave命令或者bgrewriteaof命令时,并且哈希表的负载因子大于5.

以上条件满足任意一个,Reid's将会进行rehash操作,为什么会分为在执行命令和没在执行命令两种情况呢?

因为,在执行命令时,redis需要创建当前服务进程的子进程,而大多数操作系统都采用写时复制技术来优化子进程的使用效率,所以在子进程存在期间,服务器会提高所需的负载因子,从而尽可能的避免子进程存在期间进行哈希表的扩展操作,可以避免非必要的内存写入操作,最大限度的节约内存。

rehash步骤:

1、为字典2(字段数组,第一个我称为字典1,第二个称为字典2)分配更大的空间,空间分配规则:如果执行的是扩展操作则分配第一个大于等于目前使用量*2的2的n次方幂,如果执行的是缩容操作则为字段2分配第一个大于等于目前使用量的2的n次方幂。

2、将保存在字典1上的键值对rehash到字典2上,即重新算哈希值和索引值

3、当字典1上的所有键值对全都rehash到字典2上后,释放字典1,将字典2设置为字典1,并重新创建一个字典2为下一次rehash做准备

 

当然在rehash时并不是一个性将字典1上所有的键值对全部都转移到字典2上,redis是一个单线程,当rehash到对象特别大时就会做成redis服务器无法对客户端的操作进行响应,影响使用,所以redis采取的是渐进式rehash

渐进式rehash

​​​​​​​渐进式rehash步骤:

1、为字典2分配空间,规则如上

2、将字典中的索引计数变量置为0表示rehash的开始

3、当每次对这个字典对象进行操作时,添加、删除、查找或者更新,程序除了执行制定的操作,还会将当前索引技术变量对应的位置上的所有节点进行rehash到字典2上,当rehash完成时将索引技术变量加一

4、随着操作的不断进行,最终将字典1上的所有键值对都rehash到字典2

5、释放字典1,将字典2设置为字典1,并重新创建一个字典2为下一次rehash做准备,将索引计数变量值为-1表示rehash结束

采用分而治之的方法将所需的工作均分在每次的操作上,从而避免了集中rehash带来的庞大计算量

在进行渐进式rehash时,字典的删除、查找、更新等操作都会在两个表上进行,即一个表上没找到就去另一个表,但是添加操作只会在字典2上进行,从而保证字典1有减无增

 

总结

1、字典被广泛用于redis的各种功能,其中包括数据库和哈希键

2、redis中的字典使用过哈希键作为底层实现,每个字典带有两个哈希表,一个平时使用一个在进行rehash时使用

3、当字典被用作数据库的底层实现时,redis采用murmurhash2算法来计算哈希值

4、哈希表采用拉链法解决哈希冲突,由于没有指向表尾的指针所以采用头插法

5、在对哈希表进行扩容和缩容操作时,并不是一次性全部操作完成,而是采用渐进时rehash分而治之的将所有键值对rehash到另一个键值对上的

你可能感兴趣的:(redis,数据结构)