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

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

1. 压缩列表的构成

压缩列表是 Redis 为了节约内存而开发的, 由一系列特殊编码的连续内存块组成的顺序型 (sequential) 数据结构.

一个压缩列表可以包含任意多个节点 (entry) , 每个节点可以保存一个字节数组或者一个整数值.

压缩列表的整体布局:

<zlbytes><zltail><zllen><entry><entry><zlend>

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

2 压缩列表节点的构成

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

  • 长度小于等于 63 (2^{6}-1)字节的字节数组;
  • 长度小于等于 16383 (2^{14}-1) 字节的字节数组;
  • 长度小于等于 4294967295 (2^{32}-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), 而之后的四个字节则用于保存前一节点的长度.

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

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

溯, 最终到达压缩列表的表头节点.

2.2 encoding

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

  • 一字节, 两字节或者五字节长, 值的最高位为 00 , 01 或者 10 的是字节数组编码: 这种编码表示节点的 content 属性保存着字节数组, 数组的长度由编码除去最高两位之后的其他位记录;
  • 一字节长, 值的最高位以 11 开头的是整数编码: 这种编码表示节点的 content 属性保存着整数值, 整数值的类型和长度由编码除去最高两位之后的其他位记录;
编码 编码长度 content 属性保存的值
00bbbbbb 1 字节 长度小于等于 63 字节的字节数组.
01bbbbbb xxxxxxxx 2 字节 长度小于等于 16383 字节的字节数组.
10______ aaaaaaaa bbbbbbbb cccccccc dddddddd 5 字节 长度小于等于 4294967295 的字节数组.
编码 编码长度 content 属性保存的值
11000000 1 字节 int16_t 类型的整数.
11010000 1 字节 int32_t 类型的整数.
11100000 1 字节 int64_t 类型的整数.
11110000 1 字节 24 位有符号整数.
11111110 1 字节 8 位有符号整数.
1111xxxx 1 字节 使用这一编码的节点没有相应的 content 属性, 因为编码本身的 xxxx 四个位已经保存了一个介于 0 和 12 之间的值, 所以它无须 content 属性.

2.3 content

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

3. 连锁更新

前面说过, 每个节点的 previous_entry_length 属性都记录了前一个节点的长度:

  • 如果前一节点的长度小于 254 字节, 那么 previous_entry_length 属性需要用 1 字节长的空间来保存这个长度值.
  • 如果前一节点的长度大于等于 254 字节, 那么 previous_entry_length 属性需要用 5 字节长的空间来保存这个长度值.

如果 原有的节点都小于 254 字节, 突然间插入一个大于等于 254 字节, 压缩列表将会发生空间重分配(连锁更新);

删除节点, 也会发生导致连锁更新.

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

要注意的是, 尽管连锁更新的复杂度较高, 但它真正造成性能问题的几率是很低的:

  • 首先, 压缩列表里要恰好有多个连续的, 长度介于 250 字节至 253 字节之间的节点, 连锁更新才有可能被引发, 在实际中, 这种情况并不多见;
  • 其次, 即使出现连锁更新, 但只要被更新的节点数量不多, 就不会对性能造成任何影响: 比如说, 对三五个节点进行连锁更新是绝对不会影响性能的;

因为以上原因, ziplistPush 等命令的平均复杂度仅为 O(N) , 在实际中, 我们可以放心地使用这些函数, 而不必担心连锁更新会影响压缩列表的性能.

4. 压缩列表 API

函数 作用 算法复杂度
ziplistNew 创建一个新的压缩列表. O(1)
ziplistPush 创建一个包含给定值的新节点, 并将这个新节点添加到压缩列表的表头或者表尾. 平均 O(N) ,最坏 O(N^2).
ziplistInsert 将包含给定值的新节点插入到给定节点之后. 平均 O(N) ,最坏 O(N^2).
ziplistIndex 返回压缩列表给定索引上的节点. O(N)
ziplistFind 在压缩列表中查找并返回包含了给定值的节点. 因为节点的值可能是一个字节数组, 所以检查节点值和给定值是否相同的复杂度为 O(N) , 而查找整个列表的复杂度则为 O(N^2).
ziplistNext 返回给定节点的下一个节点. O(1)
ziplistPrev 返回给定节点的前一个节点. O(1)
ziplistGet 获取给定节点所保存的值. O(1)
ziplistDelete 从压缩列表中删除给定的节点. 平均 O(N) ,最坏 O(N^2).
ziplistDeleteRange 删除压缩列表在给定索引上的连续多个节点. 平均 O(N) ,最坏 O(N^2).
ziplistBlobLen 返回压缩列表目前占用的内存字节数. O(1)
ziplistLen 返回压缩列表目前包含的节点数量. 节点数量小于 65535 时 O(1) , 大于 65535 时 O(N).

因为 ziplistPush , ziplistInsert , ziplistDelete 和 ziplistDeleteRange 四个函数都有可能会引发连锁更新, 所以它们的最坏复杂度都是 O(N^2).

5. 总结

  • 压缩列表是一种为节约内存而开发的顺序型数据结构.
  • 压缩列表被用作列表键和哈希键的底层实现之一.
  • 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值.
  • 添加新节点到压缩列表, 或者从压缩列表中删除节点, 可能会引发连锁更新操作, 但这种操作出现的几率并不高.

文章来源于本人博客,发布于 2018-06-02,原文链接:https://imlht.com/archives/141/

Redis的设计与实现(6)-压缩列表的更多相关文章

  1. Redis源码解析:07压缩列表

    压缩列表(ziplist)是列表键和哈希键的底层实现之一.当列表键只包含少量列表项,并且每个列表项要么是小整数值,要么是长度较短的字符串时:或者当哈希键只包含少量键值对,并且每个键值对的键和值要么是小 ...

  2. Redis压缩列表

    此篇文章是主要介绍Redis在数据存储方面的其中一种方式,压缩列表.本文会介绍1. 压缩列表(ziplist)的使用场景 2.如何达到节约内存的效果?3.压缩列表的存储格式 4. 连锁更新的问题  5 ...

  3. Redis 源码简洁剖析 05 - ziplist 压缩列表

    ziplist 是什么 Redis 哪些数据结构使用了 ziplist? ziplist 特点 优点 缺点 ziplist 数据结构 ziplist 节点 pre_entry_length encod ...

  4. Redis 设计与实现 5:压缩列表

    压缩列表是 ZSET.HASH和 LIST 类型的其中一种编码的底层实现,是由一系列特殊编码的连续内存块组成的顺序型数据结构,其目的是节省内存. ziplist 的结构 外层结构 下图展示了压缩列表的 ...

  5. redis 笔记01 简单动态字符串、链表、字典、跳跃表、整数集合、压缩列表

    文中内容摘自<redis设计与实现> 简单动态字符串 1. Redis只会使用C字符串作为字面量,在大多数情况下,Redis使用SDS(Simple Dynamic String,简单动态 ...

  6. Redis 学习笔记(篇四):整数集合和压缩列表

    整数集合 Redis 中当一个集合(set)中只包含整数,并且元素不多时,底层使用整数集合实现,否则使用字典实现. 那么: 为什么会出现整数集合呢?都使用字典存储不行吗? 整数集合在 Redis 中的 ...

  7. 图解Redis之数据结构篇——压缩列表

    前言     同整数集合一样压缩列表也不是基础数据结构,而是 Redis 自己设计的一种数据存储结构.它有点儿类似数组,通过一片连续的内存空间,来存储数据.不过,它跟数组不同的一点是,它允许存储的数据 ...

  8. Redis 的底层数据结构(压缩列表)

    上一篇我们介绍了 redis 中的整数集合这种数据结构的实现,也谈到了,引入这种数据结构的一个很大的原因就是,在某些仅有少量整数元素的集合场景,通过整数集合既可以达到字典的效率,也能使用远少于字典的内 ...

  9. Redis学习之ziplist压缩列表源码分析

    一.压缩列表ziplist在redis中的应用 1.做列表键 当一个列表键只包含少量列表项,并且每个列表项要么是小整数,要么是短字符串,那么redis会使用压缩列表作为列表键的底层实现 2.哈希键 当 ...

  10. 【redis】redis底层数据结构原理--简单动态字符串 链表 字典 跳跃表 整数集合 压缩列表等

    redis有五种数据类型string.list.hash.set.zset(字符串.哈希.列表.集合.有序集合)并且自实现了简单动态字符串.双端链表.字典.压缩列表.整数集合.跳跃表等数据结构.red ...

随机推荐

  1. Go/Python gRPC实践

    gRPC框架 & ProtoBuf 安装相关工具: pip3 install grpcio pip3 install grpcio-tools protobuf3有自己专门的定义的格式,基于此 ...

  2. 使用Java接入小程序订阅消息!

    更新完微信服务号的模板消息之后,我又赶紧把微信小程序的订阅消息给实现了!之前我一直以为微信小程序也是要企业才能申请,没想到小程序个人就能申请. 消息推送平台推送下发[邮件][短信][微信服务号][微信 ...

  3. 2.JAVA入门基础知识

    数据类型: java的数据类型分为两大类:基本类型和引用类型 基本类型: 整数类型: byte 一个字节 -128-127 short 2个字节 32768-32767 int 4个字节 很大 lon ...

  4. 2022-02-16:将数组分割成和相等的子数组。 给定一个有 n 个整数的数组,你需要找到满足以下条件的三元组 (i, j, k) : 0 < i, i + 1 < j, j + 1 < k < n

    2022-02-16:将数组分割成和相等的子数组. 给定一个有 n 个整数的数组,你需要找到满足以下条件的三元组 (i, j, k) : 0 < i, i + 1 < j, j + 1 & ...

  5. Python MQTT客户端 paho-mqtt

    Python中MQTT Python有许多优秀的MQTT客户端,比较有代表性的有paho-mqtt.hbmqtt.gmqtt等,各有特色 paho-mqtt 有着最优秀的文档,代码风格易于理解,同时有 ...

  6. 虚拟机linux系统密码忘记了该怎么办?

    当你的linux系统的密码忘记了该怎么办? 首先不要慌,重启电脑,开机的时候 出现这个页面的时候点击e然后出现这个页面 把里面的ro修改为 rw 修改为rw之后在这一行语句的最后面输入enforcin ...

  7. 自己动手写Docker学习笔记

    零.前言 本文为<自己动手写 Docker>的学习,对于各位学习 docker 的同学非常友好,非常建议买一本来学习. 书中有摘录书中的一些知识点,不过限于篇幅,没有全部摘录 (主要也是懒 ...

  8. 2019年蓝桥杯C/C++大学B组省赛真题(特别数的和)

    题目描述: 小明对数位中含有2.0.1.9 的数字很感兴趣(不包括前导0) 在1到40中这样的数包括1.2.9.10 至32.39 和40,共28 个,他们的和是574. 请问,在1到n 中,所有这样 ...

  9. 2023最新IntellJ IDEA诺依SpringCloud开发部署文档(保姆级别)

    目录 若依RuoYi v3.6.2部署文档 一.环境构建 二.模块描述 三.部署后端 1.下载到本地. 2.MySQL导入数据. 3.Nacos修改 (1)保证本地Nacos下载安装成功,修改本地Na ...

  10. vs2022的一些调试技巧——远程调试&线程检查&性能检查

    visual studio一直都是.net/c#开发人员最受欢迎的编译器,除了强大的代码提示和项目模板,还拥有大量的调试工具,这一期我们介绍下code freeze阶段的一些调试技巧.包括测试环境/生 ...