Redis数据结构【3】

162 阅读3分钟

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

跳表

只有zset对象的底层实现用到了跳表,跳表支持O(logn)复杂度的节点查找。

zset底层有跳表和哈希表。跳表拿来范围查询,哈希表用来常数复杂度获取权重。

跳表结构设计

多层的有序链表

跳表结构体

zskiplist

  • head
  • tail
  • length 节点数量
  • level 跳表的最大层数,层高最大的节点的层数量

跳表节点数据结构

zskiplistNode:

  • sds ele 对象元素值
  • score 权重值
  • zskiplistNode *backward; 后向指针 方便倒序查找
  • level数组 每层的前向指针和跨度 zskiplistNode *forward 和span,跨度实际上是为了计算节点在跳表中的排位

跳表节点的查询过程

跳表会从头节点的最高层开始,遍历每一层,根据SDS元素和权重判断:

  • 当前节点权重小于要查找的权重,访问该层的下一个节点
  • 当前节点权重等于要查找的权重,SDS类型数据小于要查找的数据,访问该层的下一个节点

都不满足或者下一个节点为空,遍历当前节点level数组的下一层。

\

跳表节点层数设置

跳表的相邻两层的节点数量最理想的比例是 2:1,查找复杂度可以降低到 O(logN)。

如何维持?

跳表在创建节点的时候,随机生成每个节点的层数,

具体的做法是,跳表在创建节点时候,会生成范围为[0-1]的一个随机数,如果这个随机数小于 0.25(相当于概率 25%),那么层数就增加 1 层,然后继续生成下一个随机数,直到随机数的结果大于 0.25 结束,最终确定该节点的层数

这样的做法,相当于每增加一层的概率不超过 25%,层数越高,概率越低,层高最大限制是 64

为什么用跳表而不用平衡树(如 AVL树、红黑树等)?

  • 它们不是非常内存密集型,跳表占用更少内存
  • 范围查找,链表遍历,跳表的缓存局部性比其他类型的树好
  • 易于调试,结构简单。

quicklist

3.2list底层由quicklist实现

双向链表+压缩列表。quicklist实际是一个链表,每个元素是一个压缩列表

quicklist

  • head
  • tail
  • count 总元素个数
  • len quciklistnode 的个数

quicklist节点结构

quicklistnode:

  • prev 前一个节点
  • next 下一个节点
  • zl 指向的压缩列表
  • sz 压缩列表字节大小
  • count 压缩列表元素个数

\

插入元素时,先检查插入位置的压缩列表是否能够容纳,不能就新建链表节点。

quicklist会控制压缩列表的大小和元素个数,规避连锁更新,并没有完全解决连锁更新

listpack

要想彻底解决连锁更新,需要一个新的数据结构

listpack结构设计

采用了压缩列表优秀的设计,比如连续的内存空间来保存数据,采用不同编码方式保存不同大小的数据。

  • encoding,定义该元素的编码类型,会对不同长度的整数和字符串进行编码;
  • data,实际存放的数据;
  • len,encoding+data的总长度;

可以看到,listpack 没有压缩列表中记录前一个节点长度的字段了,listpack 只记录当前节点的长度,当我们向 listpack 加入一个新元素的时候,不会影响其他节点的长度字段的变化,从而避免了压缩列表的连锁更新问题