压缩列表的构成
压缩列表是为了节省内存而开发的,可以包含任意多个节点(entry),每一个节点可以保存一个字节数组或者一个整数值。
下图展示了压缩列表的各个组成部分,
- zlbytes:4字节,压缩列表所用的字节数
- zltail:4字节,记录压缩列表尾节点entryN距离压缩列表的起始地址的字节数。
- zllen:2字节,压缩列表的节点数量
- entryX:列表节点
- zlend:1字节,特殊值
0xFF(十进制255),
用于标记压缩列表的末端
redis没有使用结构体来保存压缩列表的信息,而是通过宏来定位每个成员的地址:
/*
* ziplist 属性宏
*/
// 定位到 ziplist 的 bytes 属性,该属性记录了整个 ziplist 所占用的内存字节数
#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl)))
// 定位到 ziplist 的 offset 属性,该属性记录了到达表尾节点的偏移量
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
// 定位到 ziplist 的 length 属性,该属性记录了 ziplist 包含的节点数量
#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
// 返回 ziplist 表头的大小
#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t))
// 返回指向 ziplist 第一个节点(的起始位置)的指针
#define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE)
// 返回指向 ziplist 最后一个节点(的起始位置)的指针
#define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
// 返回指向 ziplist 末端 ZIP_END (的起始位置)的指针
#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
压缩列表节点结构
prev_entry_len成员
previous_entry_length
属性的长度可以是 1
字节或者 5
字节:
- 如果前一节点的长度小于
254
字节,previous_entry_length
的长度为1
字节。 - 如果前一节点的长度大于等于
254
字节,previous_entry_length
的长度为5
字节: 其中第一字节会被设置为0xFE
(十进制值254
), 而之后的四个字节则用于保存前一节点的长度。
下图 展示了一个压缩列表节点,previous_entry_length
为 0x05
, 表示前一节点的长度为 5
字节。
通过 previous_entry_length
属性,我们可以实现从表尾向表头遍历操作:
举个例子,有一个当前节点起始地址的指针 c
, 那么我们只要用指针 c
减去当前节点 previous_entry_length
属性的值, 就可以得出一个指向前一个节点起始地址的指针 p
。
encoding成员
redis对字节数组和整数编码提供了一组宏定义:
/*
* 字符串编码类型
*/
#define ZIP_STR_06B (0 << 6)
#define ZIP_STR_14B (1 << 6)
#define ZIP_STR_32B (2 << 6)
/*
* 整数编码类型
*/
#define ZIP_INT_16B (0xc0 | 0<<4)
#define ZIP_INT_32B (0xc0 | 1<<4)
#define ZIP_INT_64B (0xc0 | 2<<4)
#define ZIP_INT_24B (0xc0 | 3<<4)
#define ZIP_INT_8B 0xfe
value成员
value成员负责根据encoding来保存字节数组或整数
连锁更新
如果一个压缩列表中,有多个连续、长度介于250字节到253字节之间的节点,因此记录这些节点只需要1个字节的prev_entry_len,如果要插入一个长度大于等于254的新节点到压缩列表的头部,然而原来的节点的prev_entry_len成员长度仅仅为1个字节,无法保存新节点的长度,因此会对新节点之后的所有prev_entry_len成员大小为1字节的节点产生连锁更新。同样的,如果一个压缩列表中,是多个连续的长度大于等于254的节点,当往压缩列表的头部插入一个长度小于254的节点,也会产生连锁更新。另外删除节点也会产生连锁更新。
下图展示了连锁更新的过程: