Redis设计与实现第一部分:第7章:Redis-压缩列表

  压缩列表(ziplist)是列表键和哈希键的底层实现之一。

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

  当一个哈希键只包含少量键值对,并且每个键值对的键和值要么是小得整数值,或者是长度表较短的字符串,那么Redis就会使用压缩列表作为哈希键的底层实现。

  压缩列表构成:

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

  压缩列表组成部分(各个组成部分的类型、长度和用途):  

  Redis设计与实现第一部分:第7章:Redis-压缩列表

  zlbytes:unit32_t类型,4字节:记录整个一搜索列表占用的内存字节数,对压缩列表进行内存重新分配,活着计算zlend的位置时使用。

  zltail:unit32_t类型,4字节:记录压缩列表表尾节点距离要锁列表的起始地址有多少个字节:通过这个偏移量,程序无须遍历整个压缩列表就可以了确定表尾节点的地址。

  zllen:unit16_t类型,2字节:记录压缩列表包含的节点数量,当这个属性的值小于UNIT16_MAX(65535)时,这个属性的值就是压缩列表包含节点的数量;当这个属性的值等于UNIT16_MAX(65535)时,节点的真实数量需需要遍历整个压缩列表才可以计算得出。

  entryX:列表节点:,不定:压缩列表的各个节点,节点的长度由节点保存的内容决定。

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

  压缩表示例:

  Redis设计与实现第一部分:第7章:Redis-压缩列表

  zlbytes:属性值为0xd2(十进制210),表示压缩列表总裁为210字节;

  zltail:属性值为0xb3(十进制为179),这表示一个指向压缩列表起始地址的指针p,那么只要指针p加上偏移量179,就可以了计算出表尾节点entry5的地址。

  zllen:属性值为0x5(十进制5),表示压缩列表的5个节点。

  压缩列表节点的构成:

  已知每个压缩列表节点的构成是一个字节数组或者是一个整数值,其中字节数组可以是以下三种长度之一:

  1.长度小于等于63(2^6-1)字节的字节数组;

  2.长度小于等于16383(2^14-1)字节的字节数组;

  3.长度小于等于4294967195(2^32-1)字节的字节数组;

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

  1. 4位长,介于0到12之间的无符号整数;

  2. 1字节长的有符号整数;  

  3. 3字节长的有符号整数;

  4. int16_t类型整数;

  5. int32_t类型整数;

  6. int64_t类型整数;

  每个压缩列表节点都是由三个部分组成:

  Redis设计与实现第一部分:第7章:Redis-压缩列表

  1. previous_entry_length:以字节为单位,记录压缩列表中前一个节点的长度,长度可以是1字节或者5字节;

    a. 如果前一个及节点的长度小于254字节,则该属性长度为1字节:前一个节点的长度保存在这一个字节里面;

    b. 如果前一个节点的长度大于254字节,则该属性长度为5字节,其中属性的第一字节被设置为 0xFE(十进制254),而之后的四个字节保存前一个节点的长度。

  因为节点previous_entry_length属性记录了前一个节点的长度,所以程序可以通过指针运算,根据当前节点的起始地址计算出前一个节点的起始地址。

  压缩列表的从表尾指向表头遍历操作就是使用这个原理实现的,只要我们拥有了一个指向某个节点起始地址的指针,就可以了通过这个指针以及这个节点的previous_entry_length属性,程序就可以一直向前一个节点回溯,最终到达压缩列表的表头结点。

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

  1字节、2字节或者5字节长:值得最高位为00,01或者10的是字节数组编码,这种编码表示节点的content属性保存着字节数组,数组的长度由编码除去最高2位之后的其它记录。

  1字节长:值得最高位以11开头的整数编码,这种编码表示节点的content属性保存着整数值,整数值的类型和长度由编码除去最高2位之后的其它记录。

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

  压缩列表节点示例:

  Redis设计与实现第一部分:第7章:Redis-压缩列表

  连锁更新:

  假设从e1到eN的所有节点的长度都小于254字节,所以记录这些节点的长度只需要1字节长的previous_entry_length属性。这时,入股哟将一个长度大于等于254字节的新节点new设置为压缩列表的表头节点,那么new将成为e1的前置节点。

  因为e1的previous_entry_length属性仅长为1字节,它没办法保存新节点new的长度,所以程序将对压缩列表执行空间重分配操作,并将e1节点的previous_entry_length属性从原来的1字节长罗占为5字节长。然而这将会导致e1的长度大于254字节,从而又导致e2的产长度大于254字节,循环往复,会让每个节点的长度大于254字节。

  Redis将这种特殊情况下产生的连续多次空间扩展操作称之为“连锁更新”。

  除了添加新节点会导致连锁更新,删除节点也可能引发连锁更新。