• 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属性都符合压缩列表对于节点的要求,程序需要不断地对压缩列表执行空间重分配
      上述这种特殊情况下产生的连续多次空间扩展操作称之为”连锁更新“。除了添加新节点有可能引发连锁更新外,删除某个节点也有可能引发。

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

  • 相关阅读:
    52单片机独立键盘控制数码管计数
    C# 入门—实现 Hello, World!
    算法通过村第七关-树(递归/二叉树遍历)青铜笔记|手撕递归
    Ubuntu系统apt添加第三方PPA源
    C: . 与 -> 的区别
    Go的简单入门:开始使用多模块的工作空间
    数据加密标准(DES)概念及工作原理
    合作式智能运输系统通信架构
    Android 12(S) 图像显示系统 - drm_hwcomposer 简析(上)
    软考 系统架构设计师系列知识点之数字孪生体(2)
  • 原文地址:https://blog.csdn.net/weixin_41043607/article/details/125396126