什么是listpack
listpack是Redis 5.0引入的一种紧凑列表数据结构,专为优化内存占用而设计。它是为了替代旧的ziplist(压缩列表)而开发的更高效、更健壮的数据结构,在Redis 7.0及以后版本中完全取代ziplist成为默认的紧凑存储结构。
一个listpack由一系列特殊编码的连续内存块组成:
| 字段 | 大小 | 说明 |
|---|---|---|
| total_bytes | 4字节 | 记录整个listpack占用的内存总字节数 |
| num_elements | 2字节 | 记录listpack包含的元素数量 |
| entries | 变长 | listpack包含的各个节点,节点的长度由节点保存的内容决定 |
| end_marker | 1字节 | 特殊值0xFF(十进制255),用于标记listpack的末端 |
与ziplist相比,listpack去掉了zltail字段(记录表尾节点偏移量),简化了结构
listpack的节点
每个listpack节点都由三个部分组成:
-
encoding(编码类型)
- 指定元素的编码类型,记录节点保存数据的类型(整数或字符串)以及长度
- 采用可变长度编码(
Varint),根据数据大小动态选择1/2/3/5字节的编码方式 - 支持11种编码方式:7种用于整数,3种用于字符串,1种用于结束标识
-
data(实际数据)
- 保存节点的实际值,可以是字节数组或整数值
- 数据的类型和长度由节点的
encoding属性决定
-
backlen(节点总长度)
- 最关键的部分:记录从
encoding开始到backlen字段自身结束的总字节数 - 采用特殊的编码方式:每个字节的最高位为0表示结束,为1表示还有更多字节(最多5字节)
- 正是这个设计让
listpack避免了连锁更新问题
- 最关键的部分:记录从
也正是因为自身节点的长度被放在该节点的最后,因此可以取消原本记录最后一个节点的偏移量,只需要通过total_bytes获取到最后一个字节,在读取当前节点的总长度即可。
listpack如何解决连锁更新问题
连锁更新是ziplist的致命缺陷:当某个节点长度变化导致其previous_entry_length字段从1字节扩展到5字节时,会引发后续所有节点都需要更新,最坏情况下时间复杂度为O(N)。
listpack的解决方案简单而高效:
-
核心思想:每个节点只记录"我自己有多长",而不是"前一个节点有多长"
-
实现方式:通过
backlen字段记录节点自身总长度,放在节点末尾 -
优势:
- 插入、删除或修改节点时,只会影响当前操作的节点
- 不会导致后续节点的元数据信息变更
- 彻底消除了连锁反应的风险