Redis源码分析:数据结构之压缩列表(ziplist)
本节的分析基于redis-6.0.0
内容在 src/ziplist.h 和 src/ziplist.c 中
ziplist 是一个经过特殊编码的双向链表,旨在提高内存效率。 它存储字符串和整数值,其中整数被编码为实际整数而不是一系列字符。 它允许在 O(1) 时间内在列表的任一侧进行推送和弹出操作。 但是,由于每个操作都需要重新分配 ziplist 使用的内存,因此实际复杂性与 ziplist 使用的内存量有关。
ziplist的内存布局
<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
zlbytes: 32 位无符号整型,记录 ziplist 整个结构体的占用空间大小。当然了也包括 zlbytes 本身。这个结构有个很大的用处,就是当需要修改 ziplist 时候不需要遍历即可知道其本身的大小。 这个 SDS 中记录字符串的长度有相似之处,这些好的设计往往在平时的开发中可以采纳一下。
zltail: 32 位无符号整型, 记录整个 ziplist 中最后一个 entry 的偏移量。所以在尾部进行 POP 操作时候不需要先遍历一次。
zllen: 16 位无符号整型, 记录 entry 的数量, 所以只能表示 2^16。但是 Redis 作了特殊的处理:当实体数超过 2^16 ,该值被固定为 2^16 - 1。 所以这种时候要知道所有实体的数量就必须要遍历整个结构了。
entry: 真正存数据的结构。
zlend: 8 位无符号整型, 固定为 255 (0xFF)。为 ziplist 的结束标识。
值得注意的是,这个压缩列表的内存空间是连续的。这也是压缩列表的主要特点,空间连续,避免内存碎片,节省内存。
entry是链表中的一个节点,代表了一个数据。
redis中对压缩列表中节点的定义如下:
typedef struct zlentry {
unsigned int prevrawlensize; /*存储上一个节点长度的数值所需要的字节数*/
unsigned int prevrawlen; /* 上一个节点的长度 */
unsigned int lensize; /* 当前节点长度的数值所需要的字节数*/
unsigned int len; /* 当前节点的长度 */
unsigned int headersize; /* 当前节点的头部大小,值 = prevrawlensize + lensize. */
unsigned char encoding; /* 编码方式,ZIP_STR_* 或 ZIP_INT_* */
unsigned char *p; /* 指向节点内容的指针. */
} zlentry;
虽然定义了这个结构体,但是根本就没有使用zlentry结构来作为压缩列表中用来存储数据节点中的结构,因为,这个结构存小整数或短字符串实在是太浪费空间了。这个结构总共在32位机占用了28个字节(32位机),在64位机占用了32个字节。这不符合压缩列表的设计目的:提高内存的利用率。因此,在redis中,并没有定义结构体来进行操作,而是定义了一些宏,
- prev_entry_len:记录前驱节点的长度。
- encoding:记录当前节点的value成员的数据类型以及长度。
- value:根据encoding来保存字节数组或整数。