Redis(5)----浅谈压缩列表

1,前言

压缩列表(ziplist)是列表键和哈希键的底层实现之一。
当列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么是短字符串,Redis就会用压缩列表作为列表键的底层实现

Redis的其他数据结构相关如下:
简单动态字符串
链表
字典
整数集合

2,压缩列表

2.1,列表结构

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

在这里插入图片描述下面是各个部分的详细说明:

属性类型长度用途
zlbytesuint32_t4字节记录整个压缩列表占用的内存字数:在对压缩列表进行内存重分配或者计算zlend的位置时使用
zltailuint32_t4字节记录压缩列表表尾节点距离压缩列表的起始地址有多少字节:通过这个偏移量,程序无需遍历整个压缩列表就可以确定表尾节点的地址
zllenuint16_t2字节记录了压缩列表包含的节点数量:当这个属性的值小于UINT16_MAX(65535)时,这个属性的值就是压缩列表包含节点的数量;当这个值等于UINT16_MAX,节点的真是数量需要遍历整个压缩列表后才能计算得出
entryX列表节点不定压缩列表包含的各个节点,节点的长度由节点保存的内容决定
zlenduint8_t1字节特殊值0xFF(十进制255),用于标记压缩列表的末端

以下面的压缩列表为例
在这里插入图片描述

  • zlbytes:该值是0x50(十进制的80),表示压缩列表的总长度位80字节
  • zltail:该值是0x3c(十进制的60),假如此时有一个指针p指向压缩列表起始地址,那么用该指针加上偏移量60就可以计算出表尾节点entry3的地址
  • zllen:该值是0x3(十进制的3),表示该压缩列表包含三个节点

2.2,列表节点结构

每个压缩列表节点都可以保存一个字节数组或者一个整数值
如果是字节数组:

  • 长度小于等于63(26-1)字节的字节数组
  • 长度小于等于16383(214-1)字节的字节数组
  • 长度小于等于4 294 967 295(232-1)字节的字节数组

如果是整数值的话:

  • 4位长,介于0-12之间的无符号整数
  • 1字节长的有符号整数
  • 3字节长的有符号整数
  • int16_t类型整数
  • int32_t类型整数
  • int64_t类型整数

每个列表节点的结构示意图如下:
在这里插入图片描述

2.2.1,previous_entry_length

节点的previous_entry_length属性以字节为单位,记录了前一个节点的长度,该属性长度可以是一个字节也可以是五个字节。

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

示例

在这里插入图片描述

  • 图7-5表示前一个结点的长度为0x05(十进制5个字节)
  • 图7-6中,0xFE表示这是一个五字节长的previous_entry_length属性,而之后的0x00002766(十进制10086)才是前一节点的长度

好处

因为previous_entry_length记录了前一节点的长度,所以只要拿到当前节点的地址,就能实现从表尾节点向表头方向进行遍历操作(用当前指针减去previous_entry_length属性的值就能获得上一节点的起始地址)

2.2.2,encoding

上面提及到,压缩列表节点可以保存字节数组或者整数,encoding属性就是用来表示当前节点的存储数据类型和长度

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

在这里插入图片描述

2.2.3,content

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

在这里插入图片描述图7-9是一个存储字节数组的节点

  • 编码最高位是00,表示节点保存的是字节数组
  • 编码后六位是字节数组的长度,也就是十进制的11
  • content表示该节点存储的是hello world
    图7-10是一个存储整数值的节点
  • 编码11000000表示节点保存的是一个int16_t类型的整数值
  • content属性表示保存的是10086

2.3,连锁更新

回忆上面每个节点的结构中,都有一个previous_entry_length用于存储前一个节点的长度。
如果前一节点的长度小于254,则该节点的previous_entry_length用一个字节表示,反之需要五个字节。
考虑这样的一种极端情况:

  1. 一个压缩列表A中,存储了10个数据,而每个数据的长度都是介于250-253个字节之间
  2. 此时往第一个节点(称其为节点e1)前面,插入一个新的节点(称其为节点n),而这个节点的长度是大于254个字节的
  3. 这时会导致一个问题,节点e1要记录节点n的长度,并且由于节点n的长度大于254,所以e1的previous_entry_length需要由原先的一个字节扩展为五个字节
  4. 而e1的previous_entry_length扩展会导致e1节点长度介于254-257之间,这会导致e1的后续节点也引发相应的previous_entry_length扩展,并且后续的所有节点都会发生这种问题
  5. 为了让每个列表的previous_entry_length属性都符合压缩列表对于节点的要求,程序需要不断地对压缩列表执行空间重分配
    上述这种特殊情况下产生的连续多次空间扩展操作称之为”连锁更新“。除了添加新节点有可能引发连锁更新外,删除某个节点也有可能引发。

实际上连锁更新发生的条件并不常见;并且即使出现连锁更新,只要被更新的节点数量不多,就不会导致任何影响。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值