Redis学习笔记(六)--Redis底层数据结构之集合的实现原理

news2024/11/28 4:47:56

文章目录

  • 一、两种实现的选择
  • 二、ziplist
    • 1、head
    • 2、entries
    • 3、end
  • 三、listPack
    • 1、head
    • 2、entries
    • 3、end
  • 四、skipList
    • 1、skipList原理
    • 2、存在的问题
    • 3、算法优化
  • 五、quickList
    • 1、检索操作
    • 2、插入操作
    • 3、删除操作
  • 六、key与value中元素的数量

本文参考:
Redis学习汇总(已完结)
Redis超详细入门教程(基础篇)
Redis视频从入门到高级,redis视频教程详解,Redis一课在手,别无所求
黑马程序员Redis入门到实战教程,深度透析redis底层原理

Redis 中对于 Set 类型的底层实现,直接采用了 hashTable(哈希表)。但对于 Hash、ZSet、List 集合的底层实现进行了特殊的设计,使其保证了 Redis 的高性能。

一、两种实现的选择

对于Hash 与ZSet 集合,其底层的实现实际有两种:压缩列表zipList,与跳跃列表skipList。这两种实现对于用户来说是透明的,但用户写入不同的数据,系统会自动使用不同的实现。
当同时满足以配置文件 redis.conf 中相关集合元素数量阈值与元素大小阈值两个条件,使用的就是压缩列表 zipList,只要有一个条件超出使用的就是跳跃列表 skipList。例如,对于 ZSet 集合中这两个条件如下:
● 集合元素个数小于redis.conf 中 zset-max-ziplist-entries 属性的值,其默认值为 128
● 每个集合元素大小都小于 redis.conf 中zset-max-ziplist-value 属性的值,其默认值为 64字节

二、ziplist

先看第一种压缩列表ziplist,通常称为压缩列表,是一个经过特殊编码的用于存储字符串或整数的双向链表。其底层数据结构由三部分构成:head、entries 与 end。这三部分在内存上是连续存放的。
它的底层结构如下图所示:
image.png

1、head

head 又由三部分构成:
● zlbytes:占 4 个字节,用于存放整个zipList 列表的数据结构所占的字节数,也就是列表长度,它包括 zlbytes本身的长度。
● zltail:占 4 个字节,用于存放 zipList 中最后一个 entry 在整个数据结构中的偏移量(字节)。该数据的存在可以快速定位列表的尾 entry 位置,以方便操作。
● zllen:占 2 字节,用于存放列表包含的 entry 个数。由于其只有 16 位,所以 zipList 最多可以含有的 entry 个数为 216-1 = 65535 个。

2、entries

entries 是真正的列表,由很多的列表元素 entry 构成。由于不同的元素类型、数值的不同,从而导致每个entry 的长度不同。
每个entry 由三部分构成:
● prevlength:该部分用于记录上一个 entry 的长度,以实现逆序遍历。默认长度为 1 字节,只要上一个 entry 的长度<254 字节,prevlength 就占 1字节,否则其会自动扩展为 5 字节长度。
● encoding:该部分用于标志后面的 data 的具体类型。如果 data 为整数类型,encoding固定长度为 1 字节。如果 data 为字符串类型,则 encoding 长度可能会是 1 字节、2 字节或 5 字节。data 字符串不同的长度,对应着不同的 encoding 长度。
● data:真正存储的数据。数据类型只能是整数类型或字符串类型。不同的数据占用的字节长度不同。

3、end

end 只包含一部分,称为 zlend。占 1 个字节,值固定为 255,即二进制位为全 1,表示一个 zipList 列表的结束。

三、listPack

对于 ziplist,实现复杂,为了逆序遍历,每个 entry 中包含前一个entry 的长度,这样会导致在 ziplist 中间修改或者插入 entry 时需要进行级联更新。即当保存的元素数量增加或者元素变大时,压缩列表的内存空间需要进行重新分配,这可能导致性能问题,尤其是在发生连锁更新的情况下。
连锁更新是指当压缩列表中的某个节点需要更新,并且更新后的长度超过了原节点的长度时,就会触发连锁更新。这种更新会导致整个压缩列表的内存空间重新分配,影响了访问性能。
因此,由于连锁更新可能导致多次内存重分配,进而影响压缩列表的访问性能,所以压缩列表在保存的节点数量较多或者经常发生节点更新的情况下并不适用。只有在节点数量较少的情况下,即使发生连锁更新,对性能的影响也是可以接受的。
在高并发的写操作场景下会极度降低 Redis 的性能。为了实现更紧凑、更快的解析,更简单的实现,重写实现了 ziplist,并命名为 listPack。
在 Redis 7.0 中,已经将 zipList 全部替换为了 listPack,但为了兼容性,在配置中也保留了 zipList 的相关属性。
listPack 也是一个经过特殊编码的用于存储字符串或整数的双向链表。其底层数据结构也由三部分构成:head、entries 与 end,且这三部分在内存上也是连续存放的。
listPack 与zipList 的重大区别在head 与每个entry 的结构上,表示列表结束的end 与zipList的 zlend 是相同的,占一个字节,且 8 位全为 1。
image.png

1、head

head 由两部分构成:
● totalBytes:占 4 个字节,用于存放 listPack 列表整体数据结构所占的字节数,包括totalBytes 本身的长度。
● elemNum:占 2 字节,用于存放列表包含的 entry 个数。其意义与 zipList 中 zllen 的相同。与 zipList 的 head 相比,没有了记录最后一个entry 偏移量的 zltail。

2、entries

entries 也是 listPack 中真正的列表,由很多的列表元素 entry 构成。由于不同的元素类型、数值的不同,从而导致每个 entry 的长度不同。但与 zipList 的 entry 结构相比,listPack的 entry 结构发生了较大变化。
其中最大的变化就是没有了记录前一个entry 长度的 prevlength,而增加了记录当前 entry 长度的element-total-len。而这个改变仍然可以实现逆序遍历,但却避免了由于在列表中间修改或插入entry 时引发的级联更新。
每个entry 仍由三部分构成:
● encoding:该部分用于标志后面的 data 的具体类型。如果 data 为整数类型,encoding长度可能会是 1、2、3、4、5 或 9 字节。不同的字节长度,其标识位不同。如果 data为字符串类型,则encoding 长度可能会是 1、2 或 5 字节。data 字符串不同的长度,对应着不同的 encoding 长度。
● data:真正存储的数据。数据类型只能是整数类型或字符串类型。不同的数据占用的字节长度不同。
● element-total-len:该部分用于记录当前 entry 的长度,用于实现逆序遍历。由于其特殊的记录方式,使其本身占有的字节数据可能会是 1、2、3、4 或 5 字节。

3、end

与zipList的 zlend 是相同的,占一个字节,且 8 位全为 1,表示结束。

四、skipList

skipList,跳跃列表,简称跳表,是一种随机化的数据结构,基于并联的链表,实现简单,查找效率较高。简单来说跳表也是链表的一种,只不过它在链表的基础上增加了跳跃功能。也正是这个跳跃功能,使得在查找元素时,能够提供较高的效率。

1、skipList原理

假设有一个带头尾结点的有序链表。
image.png
在该链表中,如果要查找某个数据,需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点,或者找到最后尾结点,后两种都属于没有找到的情况。同样,当我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。
为了提升查找效率,在偶数结点上增加一个指针,让其指向下一个偶数结点。
image.png
这样所有偶数结点就连成了一个新的链表(简称高层链表),当然,高层链表包含的节点个数只是原来链表的一半。此时再想查找某个数据时,先沿着高层链表进行查找。当遇到第一个比待查数据大的节点时,立即从该大节点的前一个节点回到原链表中进行查找。例如,若想插入一个数据 20,则先在(8,19,31,42)的链表中查找,找到第一个比 20 大的节点 31,然后再在高层链表中找到 31 节点的前一个节点 19,然后再在原链表中获取到其下一个节点值为 23。比 20 大,则将 20 插入到 19 节点与 23 节点之间。若插入的是 25,比节点23 大,则插入到 23 节点与 31 节点之间。
该方式明显可以减少比较次数,提高查找效率。如果链表元素较多,为了进一步提升查找效率,可以将原链表构建为三层链表,或再高层级链表。层级越高,查找效率就会越高 。
image.png

2、存在的问题

这种对链表分层级的方式从原理上看确实提升了查找效率,但在实际操作时就出现了问题:由于固定序号的元素拥有固定层级,所以列表元素出现增加或删除的情况下,会导致列表整体元素层级大调整,但这样势必会大大降低系统性能。
例如,对于划分两级的链表,可以规定奇数结点为高层级链表,偶数结点为低层级链表。对于划分三级的链表,可以按照节点序号与 3 取模结果进行划分。但如果插入了新的节点,或删除了原来的某些节点,那么必定会按照原来的层级划分规则进行重新层级划分,那么势必会大大降低系统性能。

3、算法优化

为了避免前面的问题,skipList 采用了随机分配层级方式。即在确定了总层级后,每添加一个新的元素时会自动为其随机分配一个层级。这种随机性就解决了节点序号与层级间的固定关系问题。
image.png
上图演示了列表在生成过程中为每个元素随机分配层级的过程。从这个 skiplist 的创建和插入过程可以看出,每一个节点的层级数都是随机分配的,而且新插入一个节点不会影响到其它节点的层级数。只需要修改插入节点前后的指针,而不需对很多节点都进行调整。这就降低了插入操作的复杂度。
skipList 指的就是除了最下面第 1 层链表之外,它会产生若干层稀疏的链表,这些链表里面的指针跳过了一些节点,并且越高层级的链表跳过的节点越多。在查找数据的时先在高层级链表中进行查找,然后逐层降低,最终可能会降到第 1 层链表来精确地确定数据位置。在这个过程中由于跳过了一些节点,从而加快了查找速度。
当然skiplist的这种速度上的优化只有在节点数量庞大的前提下才会有显著的效果,这也是为什么redis配置文件中规定小数据量用压缩列表,大数据量用跳表。

五、quickList

image.png
快速列表(quickList)是Redis中用于实现列表(List)数据结构的底层数据结构之一。它是在Redis 3.2版本中引入的,取代了之前使用的zipList和linkedList。
快速列表本身就是一个双向无循环链表,每个节点都是一个zipList。zipList是一种紧凑存储多个数据元素的数据结构,它将多个元素按照连续的方式存储在一块连续的内存空间中,节省了存储空间。而linkedList是普通的双向链表。
快速列表将linkedList按照一定的段进行切分,每一段使用zipList来存储若干真正的数据元素。这样可以将大型列表分成多个小的zipList,每个zipList都可以紧凑地存储数据,减少了存储空间的浪费。多个zipList之间使用双向指针串接起来,形成一个完整的双向链表结构。
在配置文件中,可以通过list-max-ziplist-size属性指定每个zipList中最多可以存放多少个数据元素。当达到这个容量限制时,会自动创建一个新的zipList节点,并将新的数据元素存储在其中。
快速列表的设计吸取了zipList和linkedList的优点,避免了它们各自的不足之处。它既可以紧凑地存储数据,节省存储空间,又能够高效地支持插入、删除等操作。

1、检索操作

为了更深入的理解 quickList 的工作原理,通过对检索、插入、删除等操作的实现分析来加深理解。
对于 List 元素的检索,都是以其索引 index 为依据的。quickList 由一个个的 zipList 构成,每个 zipList 的 zllen 中记录的就是当前 zipList 中包含的 entry 的个数,即包含的真正数据元素的个数。根据要检索元素的 index,从 quickList 的头节点开始,逐个对 zipList 的 zllen 做 sum求和,直到找到第一个求和后sum 大于 index 的 zipList,那么要检索的这个元素就在这个 zipList 中。

2、插入操作

由于 zipList 是有大小限制的,所以在 quickList 中插入一个元素在逻辑上相对就比较复杂一些。假设要插入的元素的大小为 insertBytes,而查找到的插入位置所在的 zipList 当前的大小为 zlBytes,那么具体可分为下面几种情况:
● 情况一:当 insertBytes + zlBytes <= list-max-ziplist-size 时,直接插入到 zipList 中相应位置即可。
● 情况二:当 insertBytes + zlBytes > list-max-ziplist-size,且插入的位置位于该 zipList 的首部位置,此时需要查看该 zipList 的前一个 zipList 的大小 prev_zlBytes。
 ◆ 若 insertBytes + prev_zlBytes<= list-max-ziplist-size 时,直接将元素插入到前一个zipList 的尾部位置即可。
 ◆ 若 insertBytes + prev_zlBytes> list-max-ziplist-size 时,直接将元素自己构建为一个新的 zipList,并连入 quickList 中。
● 情况三:当 insertBytes + zlBytes > list-max-ziplist-size,且插入的位置位于该 zipList 的尾部位置,此时需要查看该 zipList 的后一个 zipList 的大小 next_zlBytes。
 ◆ 若 insertBytes + next_zlBytes<= list-max-ziplist-size 时,直接将元素插入到后一个 zipList 的头部位置即可。
 ◆ 若 insertBytes + next_zlBytes> list-max-ziplist-size 时,直接将元素自己构建为一个新的 zipList,并连入 quickList 中。
● 情况四:当 insertBytes + zlBytes > list-max-ziplist-size,且插入的位置位于该 zipList 的中间位置,则将当前 zipList 分割为两个 zipList 连接入 quickList 中,然后将元素插入到分割后的前面 zipList 的尾部位置。

3、删除操作

对于删除操作,只需要注意一点,在相应的 zipList 中删除元素后,该 zipList 中是否还有元素。如果没有其它元素了,则将该 zipList 删除,将其前后两个 zipList 相连接。

六、key与value中元素的数量

前面讲述的 Redis 的各种特殊数据结构的设计,不仅极大提升了Redis 的性能,并且还使得 Redis 可以支持的 key 的数量、集合value 中可以支持的元素数量可以非常庞大。
● Redis 最多可以处理 232 个 key(约 42 亿),并且在实践中经过测试,每个 Redis 实例至少可以处理 2.5 亿个 key。
● 每个 Hash、List、Set、ZSet 集合都可以包含 232 个元素。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2222538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从天边的北斗到身边的北斗 —— 探索北斗导航系统的非凡之旅

引言&#xff1a;穿越时空的导航奇迹 在浩瀚的夜空之中&#xff0c;北斗七星以其独特的排列&#xff0c;自古以来便是指引方向的天文坐标。而今&#xff0c;这份古老的智慧与现代科技完美融合&#xff0c;化作了覆盖全球的卫星导航系统——中国北斗。从遥远的星河到触手可及的…

不考虑光影、背景、装饰,你的可视化大屏摆脱不了平淡。

如果在可视化大屏的设计中不考虑光影、背景和装饰&#xff0c;确实难以摆脱平淡。光影效果可以为大屏增添立体感和层次感&#xff0c;吸引观众的注意力。 合适的背景能营造出特定的氛围&#xff0c;使数据展示更具情境感。而装饰元素则可以起到点缀和美化的作用&#xff0c;提…

【无标题】unity, 在编辑界面中隐藏公开变量和现实私有变量

1.unity, 在编辑界面中隐藏公开变量 [HideInInspector]public int Num; 2.[SerializeField]反序列化显示私有变量 SerializeField是Unity引擎中的一个特性&#xff0c;用于使私有变量在Inspector中可见并可编辑 [SerializeField] private int time; 实例效果如下图示&…

Xshell删除键不好使:删除显示退格^H

1、问题&#xff1a; Xshell不能删除&#xff0c;删除时出现 退格^H 2、解决方案&#xff1a; 点击上方&#xff1a;文件→属性→终端→键盘&#xff0c;把 delete 和 backspace 序列改为 ASCII 127即可。如下所示&#xff1a; 3、重启Xshell&#xff0c;即可以删除了。

UE5 射线折射

这个判断是否有标签是需要带有此标签的Actor来反射

基础知识 main函数形参 C语言

main函数完整的函数头&#xff1a;int main(int argc,char *argv[]) 或 int main(int argc,char **argv)arg-----argument参数c -----count个数v -----value值、内容 假设命令行上运行一个程序的命令如下&#xff1a;./test abc def 123 则test这个程序的main函数第一个…

论当前的云计算

随着技术的不断进步和数字化转型的加速&#xff0c;云计算已经成为当今信息技术领域的重要支柱。本文将探讨当前云计算的发展现状、市场趋势、技术革新以及面临的挑战与机遇。 云计算的发展现状 云计算&#xff0c;作为一种通过网络提供可伸缩的、按需分配的计算资源服务模式&a…

【AIGC】优化长提示词Prompt:提升ChatGPT输出内容的准确性与实用性

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;长提示词的挑战&#x1f4af;谷歌的优化长提示词技术关键因素分析 &#x1f4af;长提示词的设计原则&#x1f4af;优化长提示词的新框架方法&#x1f4af;实验结果分析不…

PostgreSQL的前世今生

PostgreSQL的起源可以追溯到1977年的加州大学伯克利分校&#xff08;UC Berkeley&#xff09;的Ingres项目。该项目由著名的数据库科学家Michael Stonebraker领导&#xff0c;他是2015年图灵奖的获得者。以下是PostgreSQL起源的详细概述&#xff1a; 一、早期发展 Ingres项目…

【正点原子K210连载】第四十七章 MNIST实验 摘自【正点原子】DNK210使用指南-CanMV版指南

第四十七章 MNIST实验 在上一章节中&#xff0c;介绍了利用maix.KPU模块实现了车牌的检测和识别&#xff0c;本章将继续介绍利用maix.KPU模块实现的MNIST识别。通过本章的学习&#xff0c;读者将学习到MNIST识别应用在CanMV上的实现。 本章分为如下几个小节&#xff1a; 47.1 …

058_基于python时尚女装抖音号评论数据分析系统

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…

国外白帽故事 | 攻破大学数据库系统,暴露数千学生记录

引言 在这篇文章中&#xff0c;我将分享我是如何攻破一个大型大学解决方案门户服务器的&#xff0c;这个服务器服务于许多大学客户&#xff0c;并且涉及数千名学生的数据。 目标 这是一个由印度许多大学和学院使用的门户网站&#xff0c;用于管理学生记录、成绩单、出勤记录…

【JavaEE】——四次挥手,TCP状态转换,滑动窗口,流量控制

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;断开连接的本质 二&#xff1a;四次挥手 1&#xff1a;FIN 2&#xff1a;过程梳理 …

MacOS RocketMQ安装

MacOS RocketMQ安装 文章目录 MacOS RocketMQ安装一、下载二、安装修改JVM参数启动关闭测试关闭测试测试收发消息运行自带的生产者测试类运行自带的消费者测试类参考博客&#xff1a;https://blog.csdn.net/zhiyikeji/article/details/140911649 一、下载 打开官网&#xff0c;…

华为云容器引擎(CCE):赋能企业云原生转型

在当今数字化时代&#xff0c;企业面临着日益复杂的应用部署和管理挑战。为了解决这些问题&#xff0c;容器技术应运而生&#xff0c;成为云原生架构的核心。华为云容器引擎&#xff08;CCE&#xff09;作为一款全面的容器管理解决方案&#xff0c;旨在帮助企业实现高效、灵活的…

Linux终端之旅: 打包和压缩

在 Linux 世界中&#xff0c;打包和压缩文件是管理系统资源、传输数据和备份的重要技能。通过命令行工具如 tar、gzip、zip 等&#xff0c;我们可以高效地将多个文件或目录打包为一个文件&#xff0c;并通过压缩减少其体积。接下来&#xff0c;我将记录学习如何利用这些工具&am…

SpringBoot3响应式编程全套-Spring Security Reactive

目录 传送门前言一、整合二、开发1、应用安全2、RBAC权限模型 三、认证1、静态资源放行2、其他请求需要登录 四、授权 传送门 SpringMVC的源码解析&#xff08;精品&#xff09; Spring6的源码解析&#xff08;精品&#xff09; SpringBoot3框架&#xff08;精品&#xff09; …

云+AI 时代的 OceanBase

2024 年 10 月 23 日&#xff0c;OceanBase 年度发布会在北京成功举办。会上&#xff0c;CEO 杨冰表示&#xff0c;OceanBase将继续践行一体化产品战略&#xff0c;不断演进产品能力&#xff0c;从支撑关键业务负载的OLTP能力&#xff0c;到实时分析的AP能力&#xff0c;再到应…

Unity Apple Vision Pro 保姆级开发教程 - Simulator 模拟器使用

教程视频 Apple VisionPro Simulator 模拟器使用教程 Unity Vision Pro 中文课堂教程地址&#xff1a; Unity3D Vision Pro 开发教程【保姆级】 | Unity 中文课堂 ​ VsionOS Simulator 简介 visionOS Simulator 是一个用于开发和测试 visionOS 应用程序的工具。它模拟 Appl…

Return code 0x40450037 (Not a valid nxos image)

1.问题描述 硬件&#xff1a;C93180YC&#xff08;Nexus NXOS&#xff09; 软件版本&#xff1a;Release 9.3.8 需要描述&#xff1a;需要将Nexus93180从Release 9.3.8升级到10.3.6&#xff08;M&#xff09;&#xff0c;在执行操作的时候&#xff0c;发现如下问题&#xff…