ZipList 的结构
ZipList 是 Redis 中用于实现 ZSet 的压缩数据结构,其元素采用连续存储方式,具有很高的内存紧凑性。
ZipList 结构组成如下:
- zlbytes:4字节,记录整个ziplist的字节数
- zltail:4字节,记录最后一个entry的偏移量
- zllen:2字节,记录entry数量
- Entry:实际存储的数据项
- zlend:1字节,结束标记
其中Entry的结构包含:
- prevlen:记录前一个entry的长度
- 前一个entry长度 <254:占用1字节
- 前一个entry长度 ≥254:占用5字节(首字节固定为0xFE)
- encoding:内容编码(包含类型和长度信息)
- content:实际数据
需要注意的是,prevlen采用可变长度存储方案,根据前一个entry的长度动态调整存储空间。
ZipList的级联更新问题分析
假设当前ZipList中包含3个Entry,每个Entry的总长度均为253字节。此时在Entry1后插入一个长度为300字节的新Entry,将触发以下连锁反应:
- Entry2的prevlen需要更新,因为其前驱节点变为新插入的Entry
- 由于新Entry长度超过254字节,Entry2的prevlen需要扩展为5字节
- Entry2长度增加可能导致其总长度超过254字节,进而影响Entry3的prevlen存储
- 这种连锁反应可能持续传播,最坏情况下需要更新所有后续Entry
这种级联更新机制在极端情况下(如大量连续边缘长度的Entry)会导致显著的性能损耗。
ListPack是如何解决的
ListPack为解决这一问题,直接移除了prevlen字段。其核心改动在于Entry结构的设计:
- 废弃原有prevlen字段
- 引入backlen字段记录整个Entry的字节数
- backlen位于元素末尾,采用变长存储(1-5字节)
通过这种设计,ListPack中的每个数据项只需记录自身长度,不再存储前驱节点长度。因此,进行增删操作时仅影响当前元素,无需触发级联更新,从而彻底解决了级联更新的问题。