Redis的压缩列表是什么?
作者:程序员马丁
在线博客:https://open8gu.com
大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。
答题思路
回答话术
Redis 的压缩列表是List
、Hash
和 ZSet
这三种数据结构的底层实现之一。它由一段连续的内存块组成,每一小段内存都对应一个节点。相对传统的链表,它不使用指针而使用内存偏移量记录节点间的相对关系。
1. 结构特点
压缩列表的头部记录了占用内存大小、尾节点位置与节点总数,而每个节点都记录了上一节点长度、编码格式和数据。这种简洁而紧凑的结构使其可以使用尽可能小的内存存尽可能多的数据,并且仍具备传统链表的正向与逆向遍历功能。
2. 连锁更新
压缩列表的最大问题在于,当修改节点时需要一并修改后继节点记录的前驱节点的长度,当长度超过编码类型所支持的最大数值时,后继节点也需要重新分配内存以改变编码类型。依次类推,就会导致连锁更新。
3. 紧凑列表
为了解决连锁更新问题,Redis 在后续计划引入紧凑列表(listpack
)替代压缩列表。它与压缩列表一样,都是基于一块连续的内存实现的有序列表,但是它的节点只记录当前节点的长度,而不记录前驱节点长度。因此修改节点并不会触发连锁更新。
问题详解
1. 压缩列表
Redis 的 List
、Hash
和 ZSet
都是使用压缩列表作为其实现方式的一种。
压缩列表是一种结构类似数组、由连续的整段内存组成的集合。它的每一个节点都对应其中的一小块内存。也因此如此,它的节点不需要像链表那样使用头尾指针互相关联,而是直接使用使用内存偏移量记录节点间的相对位置。
压缩列表在头部记录了三个属性:
- 列表大小(
zlbytes
),即整段列表在内存中占用的字节数。 - 尾节点位置(
zltail
),也就是从队列头到最后一个节点起始位置的内存偏 en 移量。 - 节点数量(
zllen
),即总共有多少个节点。
而每个节点中又可以划分为三个部分:
- 上一节点长度(
prelen
),用于倒序遍历时确认上一节点的起始位置。 - 节点编码(
encoding
),它同时记录了长度和编码类型。 - 数据(
data
)。
相比起传统的链表,压缩列表最大优点就是节省内存:
- 由于数据存储在一段连续的内存空间,所以它的结构十分紧凑;
- 由于使用了内存偏移量替代了链表中的指针,因此在保留正序和倒序遍历能力的同时,更节省内存;
- 由于每个节点都记录了编码,因此能针对每个节点的类型而使用最节省内存的内存分配方案;
不过对应的,压缩列表的缺点也非常明显,那就是修改成本高:
- 由于后继节点会在
prelen
记录的前驱节点的长度,因此当前驱节点修改后,后继节点就需要修改prelen
; - 当
prelen
超过当前类型编码的最大大小时,就需要改变编码类型,并重新分配内存; - 后继节点重新分配内存后,其后继节点同样面临一样的情况,如此反复,从而可能引发连锁更新。
综上考虑,Redis 一般仅在存储少量小数据的时候才会使用压缩列表,而数据比较多或者比较大的时候就会换成其他数据结构。
2. 紧凑列表
针对压缩列表修改时表现出的缺点,Redis 在更高的版本计划使用紧凑列表(listpack)替代压缩列表。
紧凑列表和压缩列表一样,同样基于一整段连续内存存储数据,不过它的列表头和节点的设计有所不同。
紧凑列表在头部记录了两个属性:
- 列表大小(
total
),即整段列表在内存中占用的字节数。 - 节点数量(
num
),即总共有多少个节点。
它的节点则包括三个部分:
- 节点长度(
len
),即encoding
+data
的总长度。正向或反向遍历依赖它完成。 - 节点编码(
encoding
)节点的编码类型。 - 数据(
data
)。
由于节点不再记录上一节点的长度,上一节点的重新分配内存后,当前节点本身不需要做任何修改,这样就避免了连锁更新。