Redis(五):压缩列表

Redis的压缩列表是为节省内存而设计的,用于列表键和哈希键的底层实现。它由一系列特殊编码的内存块组成,每个节点可存储字节数组或整数。节点的previous_entry_length记录前一个节点长度,encoding标识内容类型和长度,content保存实际数据。压缩列表在添加或删除节点时可能引发连锁更新,导致空间重分配。连锁更新不仅发生在插入,也发生在删除操作中。

压缩列表

压缩列表是列表键和哈希键的底层实现之一(列表键list的另一个底层实现是链表,哈希键hash另一个实现底层是哈希表)

当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么redis会使用压缩列表来做列表键的底层实现。

另外当一个哈希键只包含少量键值对,并且每个键值对的键和值要么就是小整数值,要么就是长度比较短的字符串,也会采用压缩列表来做哈希键的底层实现。

压缩列表的构成

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

它的结构如下图所示
在这里插入图片描述

  • zlbytes:类型为uint32_t(无符号型32位整数,长度为4字节,以16进制表示),用来记录整个压缩列表占用的内存字节数,在对压缩列表进行内存重分配时,或者计算zlend的位置时使用
  • zltail:类型为unit32_t(无符号型32位整数,长度为4字节,以16进指表示),记录压缩列表的尾结点(entryN)距离压缩列表的起始位置(zlBytes)有多少字节,其实就是一个尾到头的偏移量,通过这个属性,可以确定尾结点的地址
  • zllen:类型为unit16_t(无符号型16位整数,长度为2字节,以16进制表示),记录了压缩列表包含的结点数量:当结点数量小于 2 16 2^{16} 216时,这个属性的值就是结点的数量,但如果结点数量大于 2 16 2^{16} 216时,是需要进行遍历整个压缩列表才能得出
  • entryX:列表结点,大小不定,压缩列表包含的各个结点,结点的长度由结点保存的内容决定
  • zlend:类型为unit8_t(无符号型8位整数,长度为1字节),它是一个固定值,值为0xFF(十进制255-> 2 8 2^{8} 28),像个哨兵一样,用来标记压缩列表的末端
压缩列表结点的构成

每个压缩列表结点可以保存一个字节数组(存储字符串)或者一个整数值(存储整数),其中,字节数组可以是以下三种长度之一:

  1. 长度小于等于63字节( 2 6 − 1 2^6-1 261)的字节数组
  2. 长度小于等于 2 14 − 1 2^{14}-1 2141的字节数组
  3. 长度小于等于 2 32 − 1 2^{32}-1 2321的字节数组
  4. 即总的来说,只要字节长度小于 2 32 − 1 2^{32}-1 2321的字节数组,都可以

而整数值可以是以下6种长度之一

  1. 4位长,即0~12的无符号整数(0000 ~ 1100)
  2. 1字节长的有符号整数
  3. 3字节长的有符号整数
  4. int16_t类型的整数(有符号位16位)
  5. int32_t类型的整数(有符号位32位)
  6. int64_t类型的整数(有符号位64位)

每个结点由以下三部分组成

  • previous_entry_length
  • encoding
  • content
Previous_entry_length

这个部分的作用从名字就可以看出,它是记录了它前一个结点的长度(长度是指上一个结点所占压缩列表里面的内存长度),是以字节为单位的,而这个属性的长度也是有自己的长度,为1字节或者5字节

  • 如果前一结点的长度小于254字节( 2 8 2^8 28),那么previous_entry_length就是1字节,前一结点的长度值就保存在这1字节的previous_entry_length里
  • 如果前一结点的长度大于254字节( 2 8 2^{8} 28),那么privious_entry_length就是5字节,其中属性的第一字节会被设置为0XFE(十六进制,转为十进制就是254),后面的4个字节来储存前一结点的长度值

这个属性也是用十六进制来表示的。
在这里插入图片描述
上面的图展示两个结点,一个是previous_entry_length为0x05,另一个是0xFE00002766(这里懒得画两个个),0x05表示前一个结点的长度为5字节,0xFE00002766表示前一个结点的长度为10086字节(排除前面的0xFE,从0x00002766才是前一个结点的长度)

因为previous_entry_length属性记录了前一个结点的长度,所以可以通过指针运算,根据当前结点的其实地址来计算出上一个结点的起时地址。

压缩链表的从表尾向表头进行遍历就是使用这一原理来实现的,只要拥有了一个指向尾结点的起始地址的指针(根据压缩列表的zltail找到尾结点),就可以通过这个指针和当前的结点的previous_entry_length属性,就可以一直向前一个结点进行回溯,最终到达压缩列表的头结点,完成遍历。

encoding

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

  • 值的最高位为00、01、或10的是字节数组编码,这种编码encoding表示结点的content属性保存的是字节数组,数组的长度由编码encoding除去最高位之后的其他位来记录(就是减去了前面的2位),encoding也算是有自己的长度的,此时的encoding可以为一字节、两字节或者五字节长。
  • 值的最高位是以11开头的是整数编码:这种编码encoding表示结点的content属性保存的是整数值,整数值的类型和长度由编码除去最高位之后的其他为来记录(类型由长度决定,长度为32个位,类型就是4个字节),此时的encoding为一字节长
content

结点的content属性负责保存结点值,结点值可以是一个字节数组或者整数,值的类型和长度都是由前面提到的encoding编码来决定的。

连锁更新

每个结点都有Previous_entry_length属性,用来记录前一个结点的长度大小

  • 如果前一个结点的长度小于254字节( 2 8 2^8 28),那么Previous_entry_length大小为一个字节
  • 如果前一个结点的长度大于254字节( 2 8 2^8 28),那么previous_entry_length大小为5个字节,前一个字节来存储0xFF,后面4个字节来存储前一个结点的长度

假如在一个压缩列表里面,结点的长度都是介于250~253字节,所以里面的privious_entry_length都是一个字节(因为前一个结点都没有超过254字节,第一个结点默认为1字节,因为前面没有结点),如下图所示
在这里插入图片描述
假如下载要插入一个长度大于254字节的新结点new,插入的时候是将new设置为压缩列表的表头结点,那么new就变成了e1的前置结点,变成下面所示
在这里插入图片描述
插入的时候就会产生一个问题,e1的previous_entry_length为一字节,所以他的上一个结点长度只能小于253,但由于新插入的结点new大于253字节,是没办法保存新结点new的长度,所以此时需要对压缩列表执行空间重分配的操作,并且将e1结点的previous_entry_length属性从原来的1字节长扩展为5字节长。

这样就结束了嘛?不不不,此时会引起一连锁的反应,因为e1的previous_entry_length的属性从1字节变为5字节长,所以e1结点的长度也变大了,从原来的250 ~ 253字节变为254 ~ 257范围,导致e2也要变,一直下去,直到eN扩展

这个过程,Redis要不断地对压缩列表执行空间重分配操作,直到最后一个结点,称这个过程为连锁更新

连锁更新不仅发生在添加新结点的操作上,也可能发生在删除结点的操作上

同样也是上个栗子

不过此时e1是254字节长,而e2是253字节长,所以e3的previous_entry_length的长度为1字节(e3后面的都是253字节),但e2的previous_entry_length为5字节长,此时如果将e2删除,就变成了e1是e3的上一个结点,所以,e3的Previous_entry_length会扩展成5字节长,然后又发生连锁更新,直到最后的结点修改。

重点
  • 压缩列表是一种为节约内存而开发的顺序型数据结构
  • 压缩列表是列表键和哈希键的底层实现之一(列表键还有链表实现,哈希键还有哈希表实现)
  • 压缩列表可以包含多个结点,每个结点可以保存一个字节数组或者整数值
  • 添加或删除压缩列表的结点时,可能会发生连锁更新情况。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值