4.压缩列表

        压缩列表(ziplist)是列表键和哈希键的底层实现之一,当一个列表只包含少量列表项,并且每个列表想要么是小数,要么是长度比较短的字符串,那么redis就会使用压缩列表。

1. 压缩列表的构成

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

  • zlbytes:类型uint32_t,长度4字节,记录整个压缩列表占用的内存字节数:在对压缩列表进行内存重分配或者计算zlend的位置时使用;
  • zltail:类型uint32_t,长度4字节,记录压缩列表表尾节点距离压缩列表的起始地址有多少字节,通过这个偏移量,程序无须遍历整个压缩列表就可以确定表尾节点的地址;
  • zllen:类型uint16_t,长度2字节,记录了压缩列表包含的节点数量:当这个属性的值小于uint16_max(65535)时,这个属性的值就是压缩列表包含节点的数量;当这个值大于uint16_max时,节点的数量需要遍历整个压缩列表才能计算得出;
  • entryX:列表节点,长度不定,压缩列表包含的各个节点,节点的长度有节点保存的内容决定;
  • zlend:uint8_t,长度1字节,用于标记压缩列表的末端;

压缩列表示例:

压缩列表zlbytes属性的值为0x50,转化成十进制为80,表示压缩列表的总长为80字节;

zltail属性的值为0x3c(十进制60),表示有一个指向压缩列表起始地址的指针p,那么在指针p上加上偏移量60,就可以得出表尾节点entry3的地址;

zllen属性的值为0x3(十进制3),表示压缩列表包含三个节点;

2. 压缩列表节点的构成

每个压缩列表节点可以保存一个字节数组或者一个整数值,其中,字节数组可以时一下三种长度的其中一种:

  • 长度小于等于63(26-1)字节的字节数组;
  • 长度小于等于163383(214-1)字节的字节数组;
  • 长度小于等于4294967295(232-1)字节的字节数组;

整数值则可以时以下六种长度的其中一种:

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

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

2.1 previous_entry_length

        节点的previous_entry_length属性以字节位单位,记录了压缩列表中前一个节点的长度,previous_entry_length属性的长度可以是1字节或者5字节:如果前一节点的长度小于254字节,那么previous_entry_length属性的长度为1字节,前一节点的长度就保存在这一字节里面;如果前一字节的长度大于等于254字节,那么previous_entry_length属性的长度为5字节,其中属性的第一字节会被设置为0xFE(十进制值254),而之后的四字节则用于保存前一节点的长度。

        如下图所示,表示前一节点的长度为5字节:

        如下图所示,属性的值为0xFE00002766,其中值的最高位字节0xFE表示这是五字节长的previous_entry_length属性,而之后的四字节0x00002766(十进制值10086)才是前一节点的实际长度。

        previous_entry_length属性记录前一节点的长度,程序可以通过指针运算,根据当前节点的起始地址来计算出前一个节点的起始地址,压缩列表从表尾向表头遍历就是使用的这一原理实现的。

2.2 encoding

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

        一字节,两字节或者五字节长,值的最高位为00,01或者10的是字节数组编码:这种编码表示节点的content属性保存着字节数组,数组的长度由编码出去最高两位之后的其他位记录;

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

字节数组编码

整数编码:

2.3 content

负责保存节点的值。

示例1:encoding的值00001011,前两位00表示是字节数组类型,后六位表示字节数组的长度是11;

示例2:encoding的值11000000,前两位11表示是整数值,后六位则表示content是一个int16_t类型的整数值。

3. 连锁更新

        有一种情况,在一个压缩列表中,有多个连续的,长度介于250~253字节之间的节点,当我们将一个长度大于等于254字节的新节点new设置为压缩列表的表头节点,那么new将成为e1的前置节点,因为e1的previous_entry_length属性仅长1字节,所以程序需要将e1节点的previous_entry_length从1字节扩展为5字节,由此导致后面的多个节点的previous_entry_length属性都需要从原来的1字节扩展为5字节,这种情况下产生的连续多次空间扩展操作称为“连锁更新”。

        除了添加新节点会引发连锁更新之外,删除节点也可能引发连锁更新;

        因为连锁更新在最坏的情况下需要对压缩列表进行N次空间重分配,最坏复杂度为O(N),所以连锁更新的最坏复杂度为O(N 2)。

        虽然连锁更新的复杂度较高,但他真正造成性能问题的几率很低:首先压缩列表里正好有多个连续的,长度介于250至253字节之前的节点,才会触发连锁更新;其次,即使出现连锁更新,只要被更新的节点数量不多,也不会对性能造成影响,比如对三五个节点进行连锁更新是绝对不会影响性能的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值