Redis数据结构【2】

71 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情

哈希表

hash对象的底层实现之一是压缩列表(后来被listpack替换),还有一个就是哈希表。

哈希表结构设计

dictht

  • dictEntry **table 哈希表数组
  • size 大小
  • sizemask 大小掩码,勇于计算索引
  • used 已有的节点数量

dictEntry **table是一个指针,指向哈希表节点dictEntry

哈希表节点结构设计

dictEntry

  • key
  • v 可以是指针也可以是值,为了节省空间
  • *next 指向下一个形成链表,用链式哈希解决哈希冲突

优点和缺陷

优点:

  • key独一无二
  • O(1)复杂度查询,通过哈希函数确定位置,哈希实际上是个数组, 通过索引值能快速查询到数据

缺陷:

  • 哈希冲突,索引值一样的情况下,redis采用链式哈希的方式解决问题

哈希冲突

哈希表实际上是一个数组,每个元素就是一个哈希桶

当key经过哈希函数计算后得到哈希值,再%哈希表大小取模得到元素位置,也就是哈希桶的位置

哈希冲突就是不同的key经过哈希函数和取模运算被放到了相同的哈希桶

链式哈希

每个哈希表节点dictEntry都有一个next指针,勇于指向下一个节点,被分配到同一个哈希桶的节点可以用单链表连接起来。

随着链表长度增加,复制度就是O(n),耗时会越来越大。

解决方案就是rehash扩展

rehash

dictht表示哈希表,但在实际使用中,使用的是dict结构体,里面定义了两个哈希表

dict:

  • dictht ht[2]

表2刚开始并没有被分配空间

rehash的操作过程如下:

  • 给表2 分配空间,一般是哈希表1大两倍
  • 数据迁移到表2
  • 释放表1,表2设置为表1,创建空白表为表2

但是如果,表1数据非常大,迁移就会很慢,阻塞造成性能问题。

渐进式rehash

渐进式rehash的过程:

  • 表2分配空间
  • rehash期间,每次哈希表CRUD,就会额外把表1索引上的元素迁移到表2
  • 请求数量多了以后,最终完成迁移。完成rehash

另外,渐进式rehash过程中,查找,如果表1找不到,就会去找表2

新增直接加到表2

表1 只会减少

rehash触发条件

  • 负载因子大于等于1,如果没有执行RDB或者AOF持久化就会rehash,也就是bgsave和bgrewiteaof
  • 如果大于等于5,强制执行

整数集合

set的底层实现之一,如果set只包含整数值元素,数量不大就用整数集实现

整数集合设计

inset:

  • encoding 编码方式
  • length 元素数量
  • contents[] 保存元素的数组

即使contents被申明为int8_t类型数组,但是不包吃该类型元素,真正类型取决于enconding

  • encoding INTSET_ENC_INT16 ,int16_t类型数组

不同类型的数组,大小不同

整数集合的升级操作

如果加入的新元素的类型比集合现有元素长时,需要升级。,扩展contents数组空间,然后再加进去。

在原来数组上进行,encoding为多少就是间隔多少。

在原来的基础上扩容,如果原来为int16,现在升级为32,比如原来有3个元素,新增一个。需要扩容的大小就是432 - 316

扩容后,然后按原来的间隔分割元素,将原来的元素有序放入正确位置

好处:不是直接使用大内存,这样能节省内存资源

但不支持降级操作。删除大元素后不会降级。