压缩列表的构成

压缩列表是为了节省内存而开发的,可以包含任意多个节点(entry),每一个节点可以保存一个字节数组或者一个整数值

下图展示了压缩列表的各个组成部分,

List压缩存入redis redis压缩列表 优点_List压缩存入redis

  • 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 字节。

List压缩存入redis redis压缩列表 优点_字节数_02

 

通过 previous_entry_length 属性,我们可以实现从表尾向表头遍历操作:

举个例子,有一个当前节点起始地址的指针 c , 那么我们只要用指针 c 减去当前节点 previous_entry_length 属性的值, 就可以得出一个指向前一个节点起始地址的指针 p 。

 

 

List压缩存入redis redis压缩列表 优点_字节数_03

 

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的节点,也会产生连锁更新。另外删除节点也会产生连锁更新。

  下图展示了连锁更新的过程:

List压缩存入redis redis压缩列表 优点_#define_04

List压缩存入redis redis压缩列表 优点_字节数组_05

List压缩存入redis redis压缩列表 优点_字节数组_06