Redis学习【5】之集合的底层实现原理

news2024/11/30 6:46:39

文章目录

  • 一 集合的底层实现原理
    • 1.1 两种实现的选择
    • 1.2 zipList【存在于Redis7.0之前的版本】
    • 1.3 listPack【Redis7.0中zipList的改进版】
    • 1.4 skipList
      • 1.4.1 skipList 原理
      • 1.4.2 skipList存在的问题与优化
    • 1.5 quickList
      • 1.5.1 quitList检索操作
      • 1.5.2 quitList插入操作
      • 1.5.3 quitList删除操作
    • 1.6 key 与 value 中元素的数量

一 集合的底层实现原理

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

1.1 两种实现的选择

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

1.2 zipList【存在于Redis7.0之前的版本】

  • zipList通常称为压缩列表,是一个经过特殊编码的用于存储字符串或整数的双向链表。其底层数据结构由三部分构成:head、entries 与 end 这三部分在内存上是连续存放的。
    在这里插入图片描述
  • head的结构
    • zlbytes:占 4 个字节,用于存放 zipList 列表整体数据结构所占的字节数,包括 zlbytes本身的长度。
    • zltail:占 4 个字节,用于存放 zipList 中最后一个 entry 在整个数据结构中的偏移量(字节)。 该数据的存在可以快速定位列表的尾 entry 位置,以方便操作。
    • zllen:占 2 字节,用于存放列表包含的 entry 个数。由于其只有 16 位,所以 zipList 最多可以含有的 entry 个数为 : 2 1 6 − 1 = 65535 个 2^16-1 = 65535 个 2161=65535

  • entries的结构
    • entries 由很多的列表元素 entry 构成由于不同的元素类型、数值的不同,从而导致每个 entry 的长度不同。

  • 每个 entry 仍由三部分构成:
    • prevlength该部分用于记录上一个 entry 的长度,以实现逆序遍历。 默认长度为 1 字节,只要上一个 entry 的长度<254 字节,prevlength 就占 1 字节,否则,其会自动扩展为 3 字节长度。
    • encoding用于标志后面的 data 的具体类型。如果 data 为整数类型,encoding长度可能会是 1、2、3、4、5 或 9 字节。不同的字节长度,其标识位不同;如果 data为字符串类型,则 encoding 长度可能会是 1、2 或 5 字节。data 字符串不同的长度,对应着不同的 encoding 长度。
    • data真正存储的数据。数据类型只能是整数类型或字符串类型。不同的数据占用的字节长度不同。

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

1.3 listPack【Redis7.0中zipList的改进版】

  • 对于 ziplist,实现复杂,为了逆序遍历,每个 entry 中包含前一个 entry 的长度,这样会导致在 ziplist 中间修改或者插入 entry 时需要进行级联更新。
  • 在高并发的写操作场景下会极度降低 Redis 的性能。为了实现更紧凑、更快的解析,更简单的实现,重写实现了 ziplist,并命名为 listPack。
  • 在 Redis 7.0 中,已经将 zipList 全部替换为了 listPack,但为了兼容性,在配置中也保留了 zipList 的相关属性。
    在这里插入图片描述
  • listPack 也是一个经过特殊编码的用于存储字符串或整数的双向链表。
  • 其底层数据结构由三部分构成:head、entries 与 end,且这三部分在内存上也是连续存放的。
  • listPack与zipList的重大区别在head与每个entry的结构上:与 zipList 的 head 相比,没有了记录最后一个 entry 偏移量的 zltail;

  • head
    head 由两部分构成:
    • totalBytes:占 4 个字节,用于存放 listPack 列表整体数据结构所占的字节数,包括totalBytes 本身的长度。
    • elemNum:占 2 字节,用于存放列表包含的 entry 个数。 其意义与 zipList 中 zllen 的相同。

  • 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 字节。

  • end
    • end与zipList的 zlend 是相同的,占一个字节,且 8 位全为 1。

1.4 skipList

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

1.4.1 skipList 原理

  • 假设有一个带头尾结点的有序链表
    在这里插入图片描述
  • 在该链表中,如果要查找某个数据,需要从头开始逐个进行比较,直到找到包含数据的节点。如果找到第一个比给定数据大的节点,或者找到最后尾结点,后两种都属于没有找到的情况。同样地,当要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。
  • 为了提升查找效率,在偶数结点上增加一个指针,让其指向下一个偶数结点。
    在这里插入图片描述
  • 所有偶数结点就连成了一个新的链表(简称高层链表)。高层链表包含的节点个数只是原来链表的一半。此时再想查找某个数据时,先沿着高层链表进行查找。当遇到第一个比待查数据大的节点时,立即从该大节点的前一个节点回到原链表中进行查找。
    • 如:若想插入一个数据 20 20 20,则先在 ( 8 , 19 , 31 , 42 ) (8,19,31,42) 8193142的链表中查找,找到第一个比 20 20 20大的节 31 31 31,然后再在高层链表中找到 31 31 31 节点的前一个节点 19 19 19,然后再在原链表中获取到其下一个节点值为 23 23 23。比 20 20 20 大,则将 20 20 20 插入到 19 19 19 节点与 23 23 23 节点之间。若插入的是 25 25 25,比节点 23 23 23 大,则插入到 23 23 23 节点与 31 31 31 节点之间。

  • 该方式明显可以减少比较次数,提高查找效率。如果链表元素较多,为了进一步提升查找效率,可以将原链表构建为再高层级链表。
    在这里插入图片描述

1.4.2 skipList存在的问题与优化

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

  • 解决方法:随机层级方式
  • 为了避免前面的问题,skipList 采用了随机分配层级方式即在确定了总层级后,每添加一个新的元素时会自动为其随机分配一个层级。 这种随机性就解决了节点序号与层级间的固定关系问题。
    随机层次方式
  • 上图演示了列表在生成过程中为每个元素随机分配层级的过程。从这个 skiplist 的创建和插入过程可以看出,每一个节点的层级数都是随机分配的,而且新插入一个节点不会影响到其它节点的层级数。 **只需要修改插入节点前后的指针,而不需对很多节点都进行调整。**这
    降低了插入操作的复杂度。

  • 总结:
    • skipList 指除了最下面第 1 层链表之外,它会产生若干层稀疏的链表,这些链表里面的指针跳过一些节点,并且越高层级的链表跳过的节点越多。在查找数据的时先在高层级链表中进行查找,然后逐层降低,最终可能会降到第 1 1 1 层链表来精确地确定数据位置。由于跳过一些节点,从而加快了查找速度。

1.5 quickList

在这里插入图片描述

  • quickList(快速列表)——一个双向无循环链表,它的每一个节点都是一个zipList。
  • 从Redis3.2版本开始,对于List的底层实现,使用quickList替代了zipList 和 linkedList。zipList 与 linkedList 都存在有明显不足,而 quickList 则对它们进行了改进:吸取了 zipList 和 linkedList 的优点,避开了它们的不足。
  • quickList 本质上是 zipList 和 linkedList 的混合体其将 linkedList 按段切分,每一段使用 zipList 来紧凑存储若干真正的数据元素,多个 zipList 之间使用双向指针串接起来。 对于每个 zipList 中最多可存放多大容量的数据元素,在配置文件中通过 list-max-ziplist-size属性可以指定。

1.5.1 quitList检索操作

  • 对于 L i s t List List 元素的检索,都是以其索引 i n d e x index index 为依据的。 q u i c k L i s t quickList quickList由一个个的 z i p L i s t zipList zipList 构成,每个 z i p L i s t zipList zipList z l l e n zllen zllen 中记录的是当前 z i p L i s t zipList zipList 中包含的 e n t r y entry entry 的个数(即包含的真正数据元素的个数)。根据要检索元素的 i n d e x index index,从 q u i c k L i s t quickList quickList 的头节点开始,逐个对 z i p L i s t zipList zipList z l l e n zllen zllen s u m sum sum求和,直到找到第一个求和后 s u m sum sum 大于 i n d e x index index z i p L i s t zipList zipList,那么要检索的元素就在这个 z i p L i s t zipList zipList 中。

1.5.2 quitList插入操作

  • 由于 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 的尾部位置

1.5.3 quitList删除操作

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

1.6 key 与 value 中元素的数量

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

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

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

相关文章

知识图谱概述

知识图谱 知识图谱本质上是一种大规模的语义网络&#xff0c;富含实体、概念及其之间的各种语义关系。 作为一种语义网络是大数据时代知识表示的重要方式之一。 作为一种技术体系&#xff0c;是大数据时代知识工程代表性进展。 领域知识图谱 领 域&#xff08;行业&#xf…

一篇文章带你熟练使用Ansible中的playbook

目录 一、Playbook的功能 二、YAML 1、简介 2、特点 3、语法简介 4、YAML 列表 5、YAML的字典 三、playbook执行命令 四、 Playbook的核心组件 五、vim 设定技巧 练习 一、Playbook的功能 playbook 是由一个或多个play组成的列表 Playboot 文件使用YAML来写的 二、…

Mysql5.7安装【Windows版】

文章目录一、下载二、添加到环境变量三、添加配置文件my.ini四、安装Mysql 修改密码一、下载 下载地址 滑倒最下面有一个MySQL Community Server 选择要下载的版本 二、添加到环境变量 下载好了之后开始解压 把bin目录添加到环境变量 可以点击进入bin目录&#xff0c;直接复…

低代码平台真的是企业的福音吗?

研究低代码平台已有3年&#xff0c;也算是个低代码资深用户了&#xff0c;下面基于个人理解给大家做一份2k字的深入介绍&#xff01;希望对大家在低代码方面有一定帮助。 开篇&#xff0c;先带大家来看企业为什么要布局低代码平台&#xff01;究竟有何优势&#xff1f; &…

认识钉钉小程序_搭建一个简单的小程序---钉钉小程序开发教程001

其实这里面开发的时候具体,应该有很多的坑,不过..因为暂时不需要具体做,我仅仅查了一下怎么做,记录一下,以后不用再查了. 感觉钉钉小程序开发比微信小程序开发要更便捷,简单一些.首先要注册一个开发者,其实登录上钉钉账号就可以了.然后可以看看,快速入门,我没看 然后下载开发工…

Java基础之多线程JUC全面学习笔记

目录初识多线程多线程的实现方式常见的成员方法线程安全的问题死锁生产者和消费者线程池自定义线程池初识多线程 什么是多线程? 线程 线程是操作系统能够进行运算调度的最小单位。线程被包含在进程之中&#xff0c;是进程中的实际运作单位。 简单理解:应用软件中互相独立&…

为什么西门子、美的等企业这样进行架构升级,看看改造效果就知道了

在工业领域&#xff0c; 生产、测试、运行阶段都可能会产生大量带有时间戳的传感器数据&#xff0c;这都属于典型的时序数据。时序数据主要由各类型实时监测、检查与分析设备所采集或产生&#xff0c;涉及制造、电力、化工、工程作业等多个行业&#xff0c;具备写多读少、量非常…

从端到端打通模型端侧部署流程(NCNN)

文章目录背景介绍&#xff1a;为什么要做端侧推理&#xff1a;端侧深度学习部署流程&#xff1a;一条主要技术路线&#xff1a;ONNX&#xff1a;NCNN框架&#xff1a;NCNN的官方介绍&#xff1a;NCNN问题解决&#xff1a;NCNN使用样例快速在NCNN框架下验证自己的模型&#xff1…

数据分析思维(六)|循环/闭环思维

循环/闭环思维 1、概念 在很多的分析场景下&#xff0c;我们需要按照一套流程反复分析&#xff0c;而不是进行一次性的分析&#xff0c;也就是说这套流程的结果会成为该流程的新一次输入&#xff0c;从而形成一个闭环&#xff0c;此时的分析思维我们称之为循环/闭环思维。 常…

计算机断层扫描结肠镜和全自动骨密度仪在一次检查中的可行性

计算机断层扫描结肠镜和全自动骨密度仪在一次检查中的可行性 Feasibility of Simultaneous Computed Tomographic Colonography and Fully Automated Bone Mineral Densitometry in a Single Examination 简单总结&#xff1a; 数据&#xff1a;患者的结肠镜检查和腹部CT检查…

2022黑马Redis跟学笔记.实战篇(三)

2022黑马Redis跟学笔记.实战篇 三4.2.商家查询的缓存功能4.3.1.认识缓存4.3.1.1.什么是缓存4.3.1.2.缓存的作用1.为什么要使用缓存2.如何使用缓存3. 添加商户缓存4. 缓存模型和思路4.3.1.3.缓存的成本4.3.2.添加redis缓存4.3.3.缓存更新策略4.3.3.1.三种策略(1).内存淘汰:Redis…

NoSQL和Redis

NoSQL一、NoSqlNoSQL Not Only SQL(不仅仅是SQL)非关系型数据库二、为什么需要NoSQL1、web1.0在90年代&#xff0c;一个网站的访问量一般都不大&#xff0c;用单个数据库完全可以轻松应付。在那个时候&#xff0c;更多的都是静态网页&#xff0c;动态交互类型的网站不多。单机…

CS224W课程学习笔记(一):课程介绍与图深度学习概念

引言 我们从怎么利用图形或网络表示数据这一动机开始。网络成为了用于描述复杂系统中交互实体的通用语言。从图片上讲&#xff0c;与其认为我们的数据集由一组孤立的数据点组成&#xff0c;不如考虑这些点之间的相互作用和关系。 在不同种类的网络之间进行哲学上的区分是有启…

系统功能设计:教育缴费平台产品需求文档

教育缴费系统后台能够支撑前端业务&#xff0c;查询所需字段&#xff0c;为支撑前端业务提供服务&#xff0c;支持学校分校管理、班级分班管理、账单撤回及强制结束等功能。为了将教育缴费的需求清晰准确地描述清楚&#xff0c;本文作者编写了这个产品需求文档&#xff0c;一起…

Jmeter自带函数不够用?不如自己动手开发一个

在Jmeter的函数助手里&#xff0c;有很多内置的函数&#xff0c;比如Random、UUID、time等等。使用这些函数可以快速帮我们生成某些数据&#xff0c;进行一些逻辑处理。用起来非常的方便。 但是在实际接口测试过程中&#xff0c;有很多的需求&#xff0c;Jmeter内置的函数可能…

对抗生成网络GAN系列——Spectral Normalization原理详解及源码解析

&#x1f34a;作者简介&#xff1a;秃头小苏&#xff0c;致力于用最通俗的语言描述问题 &#x1f34a;专栏推荐&#xff1a;深度学习网络原理与实战 &#x1f34a;近期目标&#xff1a;写好专栏的每一篇文章 &#x1f34a;支持小苏&#xff1a;点赞&#x1f44d;&#x1f3fc;、…

JavaEE-HTTP协议(二)

目录HTTP请求的方法GET方法POST 方法其他方法“报头”User-AgentRefererCookieHTTP响应200 OK404 Not Found403 Forbidden405 Method Not Allowed500 Internal Server Error504 Gateway Timeout302 Move temporarily301 Moved PermanentlyHTTP请求的方法 GET方法 GET 是最常用…

Jmeter之直连数据库框架搭建简介

案例简介 通过直连数据库让程序代替接口访问数据库&#xff0c;如果二者预期结果不一致&#xff0c;就找到了程序的缺陷。 下面通过一个案例分析讲解如何实现&#xff1a;获取某个字段值&#xff0c;放在百度上搜索。 实现方式 1、Jmeter本身不具备直连数据库的功能&#xf…

机器学习笔记之生成模型综述(四)概率图模型 vs 神经网络

机器学习笔记之生成模型综述——概率图模型vs神经网络引言回顾&#xff1a;概率图模型与前馈神经网络贝叶斯网络 VS\text{VS}VS 神经网络表示层面观察两者区别推断、学习层面观察两者区别引言 本节将介绍概率图模型与神经网络之间的关联关系和各自特点。 回顾&#xff1a;概率…

Javaweb安全——Dubbo 反序列化(一)

Dubbo 反序列化&#xff08;一&#xff09; Dubbo 基础 Apache Dubbo 是一款 RPC 服务开发框架。提供三个核心功能&#xff1a;面向接口的远程方法调用、智能容错和负载均衡&#xff0c;以及服务自动注册和发现。 节点角色 节点角色说明Provider暴露服务的服务提供者Consume…