Redis设计与实现 -- 链表与字典

1. 链表

1.1 链表的结构

在 Redis 中,链表的实现是双向链表,除此之外与常规的链表不同的是它还有三个函数指针,dup 函数用于复制链表节点所保存的值,free 函数用于释放链表节点保存的值,match 函数则用于对比链表节点的值和另一个值是否相等。

1.2 总结

2. 字典

2.1 字典的定义

Redis 的字典与 C++ 的 map 类似,key 与 value 进行关联,一个字典可以包含多个键值对。

2.2 哈希表的实现

Redis 的字典底层实现为哈希表,一个哈希表有多个哈希节点,每个哈希节点保存着一个键值对。Redis 的哈希表结构定义如下。

其中 table 为一个 dictEntry 数组,每个元素保存着一个键值对, dictEntry 的结构定义如下。

为了防止哈希后键冲突,dictEntry 设计了一个 next 字段,形成了一个链表,键冲突后的结构如下图所示:

2.3 字典

在 dict 结构体中 ht 字段是一个长度为 2 的数组,每个元素都是一个 dictht 哈希表,默认使用 ht[0],ht[1] 在对 ht[0] 进行 rehash 的时候使用,trehashidx 表示 rehash 的进度,值为 -1 的时候表示目前没有在 rehash。普通状态下的 dict (没有 rehash)如下所示

2.4 哈希算法

2.5 键冲突

当有多个键被分配到哈希数组的同一索引上时,则产生了键冲突,Redis 解决该问题的方法是使用链地址法,即每个节点都有一个 next 指针, 多个哈希表的节点可以使用 next 指针连接起来,由于 dictEntry 节点组成的链表没有指向尾部的指针,如果在尾部插入需要 O(N) 的时间复杂度,考虑到效率问题,总是将新节点插入在链表的头部。

以上图为例,插入一个新的 dictEntry 节点,键值对为 k2, v2,可以看出是插入到头部的。

2.6 rehash

 当哈希表的键值对过多或者过少时,会对哈希表的大小进行相应的扩展或者收缩。rehash 的步骤如下:

哈希表的扩展收缩都是自动的,当以下条件满足时自动开始对哈希表进行扩展动作:

  • 服务器目前没有执行 BGSAVE 命令或者 BGREWRITEAOF 命令且哈希表的负载因子大于等于 1 。
  • 服务器目前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令且哈希表的负载因子大于等于 5 。

负载因子的计算公式为:ht[0].used / ht[0].size 。如一个大小为 512,包含 256 个键值对的哈希表来说, 其 负载因子为 256 / 512 = 0.5,当负载因子小于 0.1 时,自动执行收缩操作。

2.7 渐进式 rehash

哈希表的扩展和收缩是将所有的键值对 rehash 到 ht[1] 中,而这个 rehash 的过程不是一次性将所有的键值对 rehash 过去,而是分多次渐进式的进行,原因在于当键值对过多,如数百万个时,计算量过于庞大可能导致服务器在一段时间停止服务。这种渐进式的 rehash 步骤如下:

 

 

posted @ 2019-05-31 17:24  lawliet9  阅读(189)  评论(0编辑  收藏  举报