1. 什么是快速链表?
快速链表(QuickList)是 Redis 3.2 版本引入的一种列表(List)底层实现结构,它结合了双向链表 和**压缩列表(ZipList)** 的优点,在内存使用效率和操作性能之间取得了良好的折衷。
2. 为什么需要快速链表?
在 Redis 3.2 之前,列表的底层实现有两种选择:
双向链表:操作时间复杂度 O(1),但内存碎片较多,内存利用率低
压缩列表:内存紧凑,利用率高,但修改操作可能导致连锁更新,性能不稳定
快速链表的设计目标:
保持双向链表的高效操作特性
提高内存使用效率
避免压缩列表的连锁更新问题
3. 快速链表的结构设计
3.1 整体结构
QuickList
├── head: QuickListNode*
├── tail: QuickListNode*
├── count: unsigned long // 所有元素总数
├── len: unsigned long // 节点数量
├── fill: int // 单个节点最大容量限制
├── compress: int // 压缩深度
└── ...
3.2 节点结构
每个 QuickListNode 包含:
typedef struct quicklistNode {
struct quicklistNode *prev; // 前驱指针
struct quicklistNode *next; // 后继指针
unsigned char *zl; // 指向压缩列表的指针
unsigned int sz; // 压缩列表的大小
unsigned int count : 16; // 节点中元素个数
unsigned int encoding : 2; // 编码方式:RAW=1, LZF=2
unsigned int container : 2; // 容器类型:NONE=1, ZIPLIST=2
unsigned int recompress : 1; // 是否被压缩过
unsigned int attempted_compress : 1; // 测试用
unsigned int extra : 10; // 预留字段
} quicklistNode;
4. 核心特性详解
4.1 分块存储
-
将大列表分割成多个小压缩列表节点
-
每个节点的大小由
list-max-ziplist-size配置控制 -
默认单个压缩列表节点最多包含 8KB 数据
4.2 压缩机制
-
支持对中间节点进行 LZF 压缩
-
压缩策略由
list-compress-depth配置控制:-
0:不压缩(默认)
-
1:首尾各1个节点不压缩,中间节点压缩
-
2:首尾各2个节点不压缩,中间节点压缩
-
以此类推...
-
4.3 内存布局示例
QuickList
↓
Node1(zl1) ↔ Node2(zl2) ↔ Node3(zl3) ↔ ... ↔ NodeN(zlN)
↓ ↓ ↓ ↓
[entry1..n] [entry1..m] [entry1..k] [entry1..p]
5. 操作特性分析
5.1 插入操作
-
头部/尾部插入:O(1) 时间复杂度
-
中间插入:需要定位到具体节点,然后在压缩列表内插入
-
插入时如果节点超过大小限制,会进行分裂
5.2 查找操作
-
按索引查找:O(n) 时间复杂度,但比纯链表快
-
由于记录了每个节点的元素数量,可以快速定位到目标节点
5.3 删除操作
-
删除后如果节点为空,会回收该节点
-
删除后如果相邻节点都很小,可能会进行合并
6. 配置参数
6.1 list-max-ziplist-size
# 正值表示节点最多包含的entry数量
# 负值表示单个节点最大内存大小:
# -1: 4KB -2: 8KB -3: 16KB
# -4: 32KB -5: 64KB
list-max-ziplist-size -2 # 默认8KB
6.2 list-compress-depth
# 压缩深度,0表示不压缩
list-compress-depth 0 # 默认不压缩
7. 性能优势
7.1 内存效率
-
相比双向链表:内存使用减少 50%-80%
-
相比纯压缩列表:避免了大数据量时的性能抖动
7.2 操作性能
-
保持了链表 O(1) 的头尾操作性能
-
批量操作时可以利用局部性原理
7.3 避免连锁更新
-
将大压缩列表分割成小压缩列表
-
单个节点的修改不会影响整个列表
8. 使用场景
8.1 适合场景
-
需要存储大量列表数据
-
频繁进行头尾操作(LPUSH/RPOP等)
-
对内存使用敏感的应用
8.2 不适用场景
-
需要频繁按索引随机访问
-
列表元素非常大(单个元素超过64KB)
9. 实际应用示例
9.1 Redis 列表操作
# 创建列表
127.0.0.1:6379> LPUSH mylist "item1" "item2" "item3"
(integer) 3
# 查看列表信息
127.0.0.1:6379> LLEN mylist
(integer) 3
# 弹出元素
127.0.0.1:6379> RPOP mylist
"item1"
9.2 监控快速链表状态
# 查看内存使用情况
127.0.0.1:6379> MEMORY USAGE mylist
(integer) 176
# 使用redis-rdb-tools分析RDB文件
# 可以查看快速链表的详细统计信息
10. 总结
快速链表是 Redis 在列表实现上的重要优化,它:
-
✅ 结合了双向链表和压缩列表的优点
-
✅ 提供了优秀的内存使用效率
-
✅ 保持了良好的操作性能
-
✅ 避免了连锁更新问题
-
✅ 支持灵活的压缩策略