用分块双端结构保存 Redis List
Redis List 提供左右两端的插入和弹出;在 quicklist 编码下,用带头尾指针的双向节点链组织紧凑数据块。
相关概念
工程问题#
列表需要从两端更新,又要减少每个短元素独立分配节点的内存开销。列表大小、元素大小与压缩设置都会影响内部表示。
实际实现#
命令层把左、右端转换成 LIST_HEAD/LIST_TAIL,再由 quicklist 分发到头尾操作。常规节点中用 listpack 打包元素;过大的元素可单独存放,两端节点保持未压缩。
处理流程#
- 命令确定操作左端还是右端
- 按当前编码进入 listpack 或 quicklist 分支
- quicklist 直接定位头尾节点
- 更新块内元素,必要时增加或移除节点
源码片段#
固定版本 · 原文摘录src/quicklist.c7.2.0 · L1600–L1613
1600void quicklistPush(quicklist *quicklist, void *value, const size_t sz,1601 int where) {1602 /* The head and tail should never be compressed (we don't attempt to decompress them) */1603 if (quicklist->head)1604 assert(quicklist->head->encoding != QUICKLIST_NODE_ENCODING_LZF);1605 if (quicklist->tail)1606 assert(quicklist->tail->encoding != QUICKLIST_NODE_ENCODING_LZF);16071608 if (where == QUICKLIST_HEAD) {1609 quicklistPushHead(quicklist, value, sz);1610 } else if (where == QUICKLIST_TAIL) {1611 quicklistPushTail(quicklist, value, sz);1612 }1613}quicklistPush
where 决定向头部还是尾部插入,头尾节点必须保持未压缩。该入口证明双端操作的分发与端点约束;真正的数据复制、listpack 更新和节点增长在 quicklistPushHead/quicklistPushTail 内完成,因此这里不代表整次插入无条件 O(1)。
完整短函数,保留两端未压缩断言。双端删除由 quicklistPopCustom 的 HEAD/TAIL 分支交叉印证。
收益与取舍#
工程分析带来的收益
- 双端定位无需遍历全部节点
- 常规元素打包存放可减少逐元素指针开销
付出的代价
- 工程分析:块内更新可能产生数据移动或重新分配
- 节点容量、元素大小及压缩策略共同影响延迟与空间
可比较的选择
逐元素双向链表节点操作直接,但每个元素分别承担链接和分配开销;紧凑性通常较差。
版本与适用范围#
对应 Redis 7.2.0 的 List,案例重点是 quicklist 编码;该版本还支持独立 listpack 编码。双端队列描述访问接口,quicklist 描述具体表示,不能把所有 List 都画成逐元素链表。
参考来源#
源码、官方文档与原始论文相关实现与比较#
4 个相关案例伙伴分配器把空闲物理页组织为不同 order 的连续块,并用空闲链表管理同阶块。
附源码片段 · 内存管理
limit_req 把漏桶计算、红黑树查状态与队列回收组合起来,按配置的 key 管理请求速率。
附源码片段 · 通信与流量
QEMU 的 QEMUTimerList 用有序单链表维护定时器,链表头就是下一到期项。
附源码片段 · 调度与资源分配
缓存块命中后可能需要从队列中间移除,双向链表让这一步保持常数时间。
附源码片段 · 内存管理