redis 数据结构「2」

189 阅读2分钟

这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战

面试加持文章,极速面试点。主要是说几个数据结构的作用已经在 redis 的组合:

  • ziplist
  • quicklist
  • listpack
  • skiplist

ziplist

设计初衷:节省内存。在设计数据结构协议,将内存利用率发挥到机制:

  1. 每一个 entry 整体存储分为:

    1. pre_entry_lenth
    2. encoding + len → 会根据第 1 个字节分析存储类型
    3. content → 16 进制
  2. 整体分为:

    • header ⇒ [zlbytes, zltail, zllen]
    • entrys → 实际存储
    • tailer → 标记结束
  3. 从 encoding 就知道,内存分配会根据类型选择最小分配

劣势也很明显:

  1. 每一个元素存储上一个元素的 length (便于反向遍历)。在修改过程中,如果导致长度发生变化,会发生前面元素的连锁变化 → 连锁更新导致频繁申请内存
  2. 不管是反向遍历 (好歹可以反向),还是正向 ⇒ 都需要挨个遍历,在 entry 过多的情况,这是一个很耗费性能的点

为了解决这些问题,再包装一层就行 → qucklist

qucklist

简单来说:ziplist1 <-> ziplist2 <-> ziplist3 <-> ziplist4。而每一个 ziplist 其实是 quicklistNode

List 底层的数据结构就是 qucklist。因为它是一个链表,所以 LPUSH/LPOP/RPUSH/RPOP 的复杂度是 O(1) ,可以快速删除加入

优化的点:

  1. list-max-ziplist-size 限制每一个 node 可以存储的 ziplist 的数量或者是个数

    1. 正数 → 最多包含多少个数据项
    2. 负数 → 每个 ziplist 存储最大的字节数。如:-2 ⇒ 8kb
  2. 超过每一个 node 限制,会新建一个 node,然后把 ziplist 存进去

  3. List 更多的操作集中在 链头和链尾,所以可以将中间元素进行压缩,进行存储空间优化。默认不压缩 ⇒ list-compress-depth

但是没有解决 ziplist 级联更新的问题,所以出现了:listpack

listpack

本质要解决级联更新的问题,其实在存储结构 ⇒ 不要让每个元素保存上一个元素的 length 即可

存储优化:

  1. 每个元素项不再保存上一个元素的长度,而是优化元素内字段的顺序,来保证既可以从前也可以向后遍历
  2. 但是由于 List/Hash/Set/ZSet 都严重依赖 ziplist ,目前只有 stream 使用了 listpack