Redis数据结构:ZipList与Listpack
ZipList
ZipList 是一种特殊的 “双端链表”,由一系列特殊编码的连续内存块组成。可以在任意一端进行压入 / 弹出操作,并且该操作的时间复杂度为 O (1)。
其结构包含:
zlbytes
:总字节数。zltail
:尾偏移量。zllen
:entry
节点个数。- 多个
entry
:表示链表中的节点,有head
节点和tail
节点等。 zlend
:结束标示,值为0xff
。
ZipList 为节省内存,Entry 不采用普通链表记录前后节点指针的方式,而是由 previous_entry_length
(前一节点长度,根据前一节点长度大小占 1 或 5 字节)、encoding
(编码属性,记录 content
数据类型和长度,占 1、2 或 5 字节)、content
(保存节点数据,可为字符串或整数)三部分组成。
应用场景
数据量较小时,Redis 会优先使用 ZipList 存储 hash、list、zset 等数据,以此节省内存空间。
缺点
当向 ZipList 插入元素时,如果空间不足就需要重新分配连续内存空间。并且由于 ZipList 每个元素会记录前一个元素的长度(prevlen),新元素插入可能导致前一个元素的 prevlen 占用字节数发生变化,进而造成后续元素的 prevlen 字段都要依次调整,引发连锁更新,严重影响访问性能。
因此,Redis推出了Listpack来改正ZipList的缺点。
Listpack
Listpack 是 Redis 5.0 引入的新结构,目前主要应用于 Stream 数据结构中,从 Redis 6.2 版本开始,quicklist 的底层实现也由 ZipList 改为了 Listpack。
Listpack 是 Redis 为优化内存与性能推出的紧凑数据结构,用于替代部分场景下的 ZipList。
它的整体结构是连续的内存块,包含:
- lpbytes:记录 Listpack 占用的总字节数。
- lplen:表示 Listpack 中元素的个数。
- 多个 entry:是 Listpack 存储数据的基本单元,有头节点、尾节点等。
- lpending(可选,部分场景存在):用于记录一些待处理的信息,比如可能涉及的增量更新相关标识等。
- lpend:结束标识,标志 Listpack 的结尾。
Listpack 里的 Entry 为提升效率,不再因前一元素长度变化引发连锁更新。每个 Entry 由以下部分组成:
- encoding:编码属性,记录
data
的数据类型(如字符串、整数等)以及长度,占用 1、2 或 5 字节。 - data:负责保存节点的实际数据,可存储字符串或整数等。
- length:当前 Entry 自身的长度,采用变长编码,能根据实际长度灵活占用 1 至 5 字节,且每个 Entry 仅记录自身长度,元素操作时互不影响,避免了 ZipList 可能出现的连锁更新问题,让内存分配和更新更稳定高效。
优势
Listpack 最大的优势是解决了 ZipList 的连锁更新问题。因为每个列表项仅记录自身长度,在新增或修改元素时,不会影响后续列表项的长度,每个元素操作相互独立,使得内存分配与更新的性能更加稳定。