Redis的数据结构(7):listpack

21 阅读2分钟

什么是listpack

listpackRedis 5.0引入的一种紧凑列表数据结构,专为优化内存占用而设计。它是为了替代旧的ziplist(压缩列表)而开发的更高效、更健壮的数据结构,在Redis 7.0及以后版本中完全取代ziplist成为默认的紧凑存储结构。

一个listpack由一系列特殊编码的连续内存块组成:

字段大小说明
total_bytes4字节记录整个listpack占用的内存总字节数
num_elements2字节记录listpack包含的元素数量
entries变长listpack包含的各个节点,节点的长度由节点保存的内容决定
end_marker1字节特殊值0xFF(十进制255),用于标记listpack的末端

ziplist相比,listpack去掉了zltail字段(记录表尾节点偏移量),简化了结构

listpack的节点

每个listpack节点都由三个部分组成:

  1. encoding(编码类型)

    • 指定元素的编码类型,记录节点保存数据的类型(整数或字符串)以及长度
    • 采用可变长度编码(Varint),根据数据大小动态选择1/2/3/5字节的编码方式
    • 支持11种编码方式:7种用于整数,3种用于字符串,1种用于结束标识
  2. data(实际数据)

    • 保存节点的实际值,可以是字节数组或整数值
    • 数据的类型和长度由节点的encoding属性决定
  3. backlen(节点总长度)

    • 最关键的部分:记录从encoding开始到backlen字段自身结束的总字节数
    • 采用特殊的编码方式:每个字节的最高位为0表示结束,为1表示还有更多字节(最多5字节)
    • 正是这个设计让listpack避免了连锁更新问题

也正是因为自身节点的长度被放在该节点的最后,因此可以取消原本记录最后一个节点的偏移量,只需要通过total_bytes获取到最后一个字节,在读取当前节点的总长度即可。

listpack如何解决连锁更新问题

连锁更新是ziplist的致命缺陷:当某个节点长度变化导致其previous_entry_length字段从1字节扩展到5字节时,会引发后续所有节点都需要更新,最坏情况下时间复杂度为O(N)。

listpack的解决方案简单而高效:

  • 核心思想:每个节点只记录"我自己有多长",而不是"前一个节点有多长"

  • 实现方式:通过backlen字段记录节点自身总长度,放在节点末尾

  • 优势

    1. 插入、删除或修改节点时,只会影响当前操作的节点
    2. 不会导致后续节点的元数据信息变更
    3. 彻底消除了连锁反应的风险