Redis的数据结构(6):压缩列表

0 阅读4分钟

什么是压缩列表

压缩列表是Redis为了节约内存而开发的, 由一系列特殊编码的连续内存块组成的顺序型数据结构。

一个压缩列表可以包含任意多个节点, 每个节点可以保存一个字节数组或者一个整数值。

image.png

zlbytes记录整个压缩列表占用的内存字节数

zltial记录压缩列表表尾节点距离压缩列表的起始地址有多少字节

zzlen记录了压缩列表包含的节点数量: 当这个属性的值小于 UINT16_MAX (65535)时, 这个属性的值就是压缩列表包含节点的数量; 当这个值等于 UINT16_MAX 时, 节点的真实数量需要遍历整个压缩列表才能计算得出。

entry压缩列表包含的各个节点,节点的长度由节点保存的内容决定。

zlend特殊值 0xFF (十进制 255 ),用于标记压缩列表的末端。

下面展示一个压缩列表的例子:

  • 列表 zlbytes 属性的值为 0x50 (十进制 80), 表示压缩列表的总长为 80 字节。
  • 列表 zltail 属性的值为 0x3c (十进制 60), 这表示如果我们有一个指向压缩列表起始地址的指针 p , 那么只要用指针 p 加上偏移量 60 , 就可以计算出表尾节点 entry3 的地址。
  • 列表 zllen 属性的值为 0x3 (十进制 3), 表示压缩列表包含三个节点。

image.png

压缩列表的节点

每个压缩列表节点都由 previous_entry_length 、 encoding 、 content 三个部分组成

image.png

previous_entry_length

节点的 previous_entry_length 属性以字节为单位, 记录了压缩列表中前一个节点的长度。

其长度是1字节或者5字节:

  1. 如果前一节点的长度小于 254 字节, 那么 previous_entry_length 属性的长度为 1 字节: 前一节点的长度就保存在这一个字节里面。
  2. 如果前一节点的长度大于等于 254 字节, 那么 previous_entry_length 属性的长度为 5 字节: 其中属性的第一字节会被设置为 0xFE(十进制值 254), 而之后的四个字节则用于保存前一节点的长度。

比如,前一个节点的长度为5字节

image.png

前一个节点的长度为10086字节

image.png

因为这个属性,程序可以通过指针运算,根据当前的起始地址来计算出前一个节点的起始地址。 因此压缩列表可以从表头向表尾进行遍历。

下面举一个例子:

  • 首先,我们拥有指向压缩列表表尾节点 entry4 起始地址的指针 p1 (指向表尾节点的指针可以通过指向压缩列表起始地址的指针加上zltail 属性的值得出);
  • 通过用 p1 减去 entry4 节点 previous_entry_length 属性的值, 我们得到一个指向 entry4 前一节点 entry3 起始地址的指针 p2 ;
  • 通过用 p2 减去 entry3 节点 previous_entry_length 属性的值, 我们得到一个指向 entry3 前一节点 entry2 起始地址的指针 p3 ;
  • 通过用 p3 减去 entry2 节点 previous_entry_length 属性的值, 我们得到一个指向 entry2 前一节点 entry1 起始地址的指针 p4 , entry1为压缩列表的表头节点;

image.png

encoding

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

  • 一字节、两字节或者五字节长, 值的最高位为 00 、 01 或者 10 的是字节数组编码: 这种编码表示节点的 content 属性保存着字节数组, 数组的长度由编码除去最高两位之后的其他位记录;
  • 一字节长, 值的最高位以 11 开头的是整数编码: 这种编码表示节点的 content 属性保存着整数值, 整数值的类型和长度由编码除去最高两位之后的其他位记录;

content

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

连锁更新

因为每一个压缩列表的节点都保存着前一个节点的长度,并且这个长度有两种可能:1字节或者5字节。

那么考虑这种情况,假设现在所有的压缩列表的节点的长度都是251,这时,所有的的节点的previous_entry_length长度都是1字节。

此时插入一个长度大于等于254字节的新节点在列表的头部,那么第二个节点的previous_entry_length将从1字节变成5字节,第二个节点的整体长度变成255字节,这样第三个节点的previous_entry_length也会从1字节变成5字节,整体长度也会变成255字节,这样就会产生连锁更新,直到最后的节点。

image.png 同时,删除一个节点也可能会产生连锁更新。