1_6_Redis设计与实现读书记-压缩列表

297 阅读4分钟

压缩列表

是列表建和哈希键底层实现之一。

一个列表键只包含少量列表项,每个列表项不是小整数值就是比较短的字符串,此时Redis就会使用压缩列表来做列表键的底层实现。

当一个哈希键值包含少量键值对,每个键值对的键和值要么就是小整数值,要么就是长度比较短的字符串,则Redis就会使用压缩列表做哈希键的底层实现。

哈希键里包含的所有键和值都是小整数值或者短字符串。

1. 压缩列表的构成

压缩列表是Redis为了节约内存而开发的,由一些列特殊编码的连续内存块组成的顺序型的数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。

2.压缩列表节点的构成

每个压缩列表节点可以保存一个字节数组或者一个整数值。

字节数组的种类:

  1. 长度小于等于63 字节的字节数组。
  2. 长度小于等于16383字节的字节数组。
  3. 长度小于等于4294967295字节的字节数组。

整数值的种类:

  1. 4位长,介于0至12之间的无符号整数。
  2. 1字节长的有符号整数。
  3. 3字节长的有符号整数。
  4. int16_t类型整数。
  5. int32_t类型整数。
  6. int64_t类型整数。

每个节点都由previous_entry_length、encoding、content三个部分组成。

2.1 previous_entry_length

以字节为单位,记录表中前一个节点的长度。长度可以是1字节或者5字节。

  1. 如果前一个节点长度小于254字节,则长度为1字节。前一节点的长度就保存在这一个字节里面。
  2. 如果前一个节点长度大于254字节,则长度为5字节。属性的第一个字节设置为0xFE(十进制为254),之后的四个字节则保存前一节点的长度。
    因为节点的previous_entry_length属性记录了前一个节点的长度,则可以根据当前节点的起始地址,计算出前一个节点的起始地址。

只要知道一个指向某个节点起始地址的指针,则可以通过这个指针以及这个节点的previous_entry_length属性,程序可以一直向前,最终找到列表的表头节点。

示例:查找头结点的过程

2.2 encoding

节点的encoding属性记录了节点content属性所保存数据的类型以及长度。

(1)一字节、两字节或者五字节长,值的最高位为00、01或者10的是字节数组编码,表示节点content属性保存着字节数组,数组长度由编码除去最高两位之后的其他位记录。

(2)一字节长,值的最高位以11开头的是整数编码,编码表示节点的content属性保存着整数值,整数值的类型和长度由编码除去最高两位之后的其他记录。

2.3 content

节点的content属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。

3. 连锁更新

示例:

​ 在一个压缩列表中,有多个连续、长度介于250字节到253字节之间的节点,在一个长度大于254的新节点new设置为压缩列表的表头节点,则t1保存这个new节点的previous_entry_length的长度则由1字节变为5字节,则t1节点的长度变为254到257之间,则保存t1长度的t2节点长度也需要修改,依次引起的压缩列表的空间的重新分配,直到最后一个节点为止。

在这种特殊情况下产生的连续多次空间扩展操作称之为"连锁更新”(cascade update)。

新增节点,引发的连锁更新:

删除节点,引发的连锁更新:

在连锁更新中,最坏的情况需要对压缩列表执行N次空间重分配操作,而每次空间重分配的最坏复杂度为O(N),连锁更新的最坏复杂度为O(N^2)。

**注:**尽管连锁更新的复杂度比较高,但是真正造成性能问题的几率很低。

  • 压缩表里恰好有多个连续的、长度介于250到253字节之间的点
  • 如果出现连锁更新,只要更新的节点数量不多,就不会对性能产生任何影响。

则,ziplistPush等命令的平均时间复杂度为O(N)。

4. 压缩列表 API

5. 回顾

  • 压缩列表是一种节约内存而开发的 顺序型数据结构
  • 可被用作列表键和哈希键的底层实现之一。
  • 可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
  • 添加节点或者删除节点,可能会引发连锁更新操作。