Redis经典五大类型源码及底层实现(二)

news2025/1/16 21:42:17
  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring源码、JUC源码、Kafka原理、分布式技术原理、数据库技术
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

文章目录

  • Redis经典五大类型源码及底层实现
    • Hash数据结构介绍
      • redis7
        • 源码分析
        • 明明已经有ziplist了,为什么出来一个listpack紧凑列表呢?
          • ziplist的连锁更新问题
        • listpack结构
          • entry结构
        • ziplist内存布局 VS listpack内存布局
    • List数据结构
      • Redis6
      • Redis版本前List的一种编码格式
      • 源码分析
        • quicklist结构
        • quicklistNode结构
      • Redis7
        • 源码实现
    • Set数据结构介绍
      • 源码分析
    • ZSet数据结构介绍
      • Redis6
      • Redis7
      • 源码分析
        • Redis6
        • Redis7
      • skiplist
        • 优化
        • 优化二
        • 是什么?
        • 跳表时间 + 空间复杂度介绍
          • 时间复杂度
          • 空间复杂度
        • 优缺点

Redis经典五大类型源码及底层实现

Hash数据结构介绍

redis7

listpack+hashtable

hash-max-listpack-entries:使用压缩列表保存时哈希集合中的最大元素个数。

hash-max-listpack-value:使用压缩列表保存时哈希集合中单个元素的最大长度。

Hash类型键的字段个数 小于 hash-max-listpack-entries且每个字段名和字段值的长度 小于 hash-max-listpack-value 时,

Redis才会使用OBJ_ENCODING_LISTPACK来存储该键,前述条件任意一个不满足则会转换为 OBJ_ENCODING_HT的编码方式

在这里插入图片描述

结论:

1.哈希对象保存的键值对数量小于512个

2.所有的键值对的键和值的字符串长度都小于等于64byte(一个英文字母一个字节)时用listpack,反之用hashtable

3.listpack升级到hashtable可以,反过来降级不可以

在这里插入图片描述

源码分析

实现:object.c

在这里插入图片描述

实现:listpack.c

在这里插入图片描述

lpNew 函数创建了一个空的 listpack,一开始分配的大小是 LP_HDR_SIZE 再加 1 个字节。LP_HDR_SIZE 宏定义是在 listpack.c 中,它默认是 6 个字节,其中 4 个字节是记录 listpack 的总字节数,2 个字节是记录 listpack 的元素数量。

此外,listpack 的最后一个字节是用来标识 listpack 的结束,其默认值是宏定义 LP_EOF。

和 ziplist 列表项的结束标记一样,LP_EOF 的值也是 255

实现:object.c

在这里插入图片描述

明明已经有ziplist了,为什么出来一个listpack紧凑列表呢?

在这里插入图片描述

ziplist的连锁更新问题

压缩列表新增某个元素或修改某个元素时,如果空间不不够,压缩列表占用的内存空间就需要重新分配。而当新插入的元素较大时,可能会导致后续元素的 prevlen 占用空间都发生变化,从而引起「连锁更新」问题,导致每个元素的空间都要重新分配,造成访问压缩列表性能的下降。

案例说明:压缩列表每个节点正因为需要保存前一个节点的长度字段,就会有连锁更新的隐患

第一步:现在假设一个压缩列表中有多个连续的、长度在 250~253 之间的节点,如下图:

在这里插入图片描述

因为这些节点长度值小于 254 字节,所以 prevlen 属性需要用 1 字节的空间来保存这个长度值,一切OK,O(∩_∩)O哈哈~

第二步:这时,如果将一个长度大于等于 254 字节的新节点加入到压缩列表的表头节点,即新节点将成为entry1的前置节点,如下图:

在这里插入图片描述

因为entry1节点的prevlen属性只有1个字节大小,无法保存新节点的长度,此时就需要对压缩列表的空间重分配操作并将entry1节点的prevlen 属性从原来的 1 字节大小扩展为 5 字节大小。

第三步:连续更新问题出现

在这里插入图片描述

entry1节点原本的长度在250~253之间,因为刚才的扩展空间,此时entry1节点的长度就大于等于254,因此原本entry2节点保存entry1节点的 prevlen属性也必须从1字节扩展至5字节大小。entry1节点影响entry2节点,entry2节点影响entry3节点…一直持续到结尾。这种在特殊情况下产生的连续多次空间扩展操作就叫做「连锁更新」

结论:listpack 是 Redis 设计用来取代掉 ziplist 的数据结构,它通过每个节点记录自己的长度且放在节点的尾部,来彻底解决掉了 ziplist 存在的连锁更新的问题

listpack结构

在这里插入图片描述

Total Bytes为整个listpack的空间大小,占用4个字节,每个listpack最多占用4294967295Bytes。
num-elements为listpack中的元素个数,即Entry的个数占用2个字节
element-1~element-N为每个具体的元素
listpack-end-byte为listpack结束标志,占用1个字节,内容为0xFF。

在这里插入图片描述

entry结构
  • 当前元素的编码类型
  • 元素数据
  • 以及编码类型和元素数据这两部分的长度
ziplist内存布局 VS listpack内存布局

在这里插入图片描述

和ziplist 列表项类似,listpack 列表项也包含了元数据信息和数据本身。不过,为了避免ziplist引起的连锁更新问题,listpack 中的每个列表项

不再像ziplist列表项那样保存其前一个列表项的长度。

在这里插入图片描述

List数据结构

Redis6

在这里插入图片描述

(1) ziplist压缩配置:list-compress-depth 0

​ 表示一个quicklist两端不被压缩的节点个数。这里的节点是指quicklist双向链表的节点,而不是指ziplist里面的数据项个数

参数list-compress-depth的取值含义如下:

0: 是个特殊值,表示都不压缩。这是Redis的默认值。

1: 表示quicklist两端各有1个节点不压缩,中间的节点压缩。

2: 表示quicklist两端各有2个节点不压缩,中间的节点压缩。

3: 表示quicklist两端各有3个节点不压缩,中间的节点压缩。

依此类推…

(2) ziplist中entry配置:list-max-ziplist-size -2

当取正值的时候,表示按照数据项个数来限定每个quicklist节点上的ziplist长度。比如,当这个参数配置成5的时候,表示每个quicklist节点的ziplist最多包含5个数据项。当取负值的时候,表示按照占用字节数来限定每个quicklist节点上的ziplist长度。这时,它只能取-1到-5这五个值,

每个值含义如下:

-5: 每个quicklist节点上的ziplist大小不能超过64 Kb。(注:1kb => 1024 bytes)

-4: 每个quicklist节点上的ziplist大小不能超过32 Kb。

-3: 每个quicklist节点上的ziplist大小不能超过16 Kb。

-2: 每个quicklist节点上的ziplist大小不能超过8 Kb。(-2是Redis给出的默认值)

-1: 每个quicklist节点上的ziplist大小不能超过4 Kb。

Redis版本前List的一种编码格式

list用quicklist存储,quicklist存储了一个双向链表,每个节点都是一个ziplist

在这里插入图片描述

在Redis3.0之前,list采用的底层数据结构是ziplist压缩列表+linkedList双向链表

然后在高版本的Redis中底层数据结构是quicklist(替换了ziplist+linkedList),而quicklist也用到了ziplist

结论:quicklist就是「双向链表 + 压缩列表」组合,因为一个 quicklist 就是一个链表,而链表中的每个元素又是一个压缩列表

在这里插入图片描述

quicklist 实际上是 zipList 和 linkedList 的混合体,它将 linkedList按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。

在这里插入图片描述

源码分析

quicklist.h,head和tail指向双向列表的表头和表尾

quicklist结构

在这里插入图片描述

quicklistNode结构

在这里插入图片描述

quicklistNode中的*zl指向一个ziplist,一个ziplist可以存放多个元素

在这里插入图片描述

Redis7

在这里插入图片描述

listpack紧凑列表

是用来替代 ziplist 的新数据结构,在 7.0 版本已经没有 ziplist 的配置了(6.0版本仅部分数据类型作为过渡阶段在使用)

源码实现

本图最下方有lpush命令执行后直接调用pushGenericCommand命令

在这里插入图片描述

看看redis6的相同文件t_list.c

在这里插入图片描述

实现:object.c

在这里插入图片描述

Redis7的List的一种编码格式,list用quicklist存储,quicklist存储了一个双向链表,每个节点都是一个listpack

quicklist是listpack和linkedlist的结合体

Set数据结构介绍

Redis用intset或hashtable存储set。如果元素都是整数类型,就用intset存储。

如果不是整数类型,就用hashtable(数组+链表的存来储结构)。key就是元素的值,value为null。

在这里插入图片描述

Set的两种编码格式

intset

hashtable

源码分析

在这里插入图片描述

ZSet数据结构介绍

Redis6

当有序集合中包含的元素数量超过服务器属性 server.zset_max_ziplist_entries 的值(默认值为 128 ),

或者有序集合中新添加元素的 member 的长度大于服务器属性 server.zset_max_ziplist_value 的值(默认值为 64 )时,

redis会使用跳跃表作为有序集合的底层实现。

否则会使用ziplist作为有序集合的底层实现

在这里插入图片描述

在这里插入图片描述

Redis7

在这里插入图片描述

ZSet的两种编码格式

redis6:ziplist + skiplist

redis7:listpack + skiplist

源码分析

Redis6

在这里插入图片描述

Redis7

在这里插入图片描述

skiplist

为什么引出跳表

先从一个单链表来讲

对于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。

这样查找效率就会很低,时间复杂度会很高O(N)

在这里插入图片描述

但是存在痛点:

在这里插入图片描述

解决方法:升维,也叫空间换时间。

优化

在这里插入图片描述

从这个例子里,我们看出,加来一层索引之后,查找一个结点需要遍历的结点个数减少了,也就是说查找效率提高了。

优化二

画一个包含64个节点的链表,按照前面讲的这种思路,建立五级索引

在这里插入图片描述

是什么?

skiplist是一种以空间换取时间的结构。

由于链表,无法进行二分查找,因此借鉴数据库索引的思想,提取出链表中关键节点(索引),先在关键节点上查找,再进入下层链表查找,提取多层关键节点,就形成了跳跃表

but

由于索引也要占据一定空间的,所以,索引添加的越多,空间占用的越多

总体来讲 跳表 = 链表 + 多级索引

跳表时间 + 空间复杂度介绍
时间复杂度

跳表查询的时间复杂度分析,如果链表里有N个结点,会有多少级索引呢?

按照我们前面讲的,两两取首。每两个结点会抽出一个结点作为上一级索引的结点,以此估算:

第一级索引的结点个数大约就是n/2,

第二级索引的结点个数大约就是n/4,

第三级索引的结点个数大约就是n/8,依次类推…

也就是说,第k级索引的结点个数是第k-1级索引的结点个数的1/2,那第k级索引结点的个数就是n/(2^k)

在这里插入图片描述

空间复杂度

跳表查询的空间复杂度分析

比起单纯的单链表,跳表需要存储多级索引,肯定要消耗更多的存储空间。那到底需要消耗多少额外的存储空间呢?

我们来分析一下跳表的空间复杂度。

第一步:首先原始链表长度为n,

第二步:两两取首,每层索引的结点数:n/2, n/4, n/8 … , 8, 4, 2 每上升一级就减少一半,直到剩下2个结点,以此类推;如果我们把每层索引的结点数写出来,就是一个等比数列。

在这里插入图片描述

这几级索引的结点总和就是n/2+n/4+n/8…+8+4+2=n-2。所以,跳表的空间复杂度是O(n) 。也就是说,如果将包含n个结点的单链表构造成跳表,我们需要额外再用接近n个结点的存储空间。

第三步:思考三三取首,每层索引的结点数:n/3, n/9, n/27 … , 9, 3, 1 以此类推;

第一级索引需要大约n/3个结点,第二级索引需要大约n/9个结点。每往上一级,索引结点个数都除以3。为了方便计算,我们假设最高一级的索

引结点个数是1。我们把每级索引的结点个数都写下来,也是一个等比数列

在这里插入图片描述

通过等比数列求和公式,总的索引结点大约就是n/3+n/9+n/27+…+9+3+1=n/2。尽管空间复杂度还是O(n) ,但比上面的每两个结点抽一个结点的索引构建方法,要减少了一半的索引结点存储空间。

所以空间复杂度是O(n);

优缺点

优点:

跳表是一个最典型的空间换时间解决方案,而且只有在数据量较大的情况下才能体现出来优势。而且应该是读多写少的情况下才能使用,所以它的适用范围应该还是比较有限的

缺点:

维护成本相对要高,

在单链表中,一旦定位好要插入的位置,插入结点的时间复杂度是很低的,就是O(1)

but

新增或者删除时需要把所有索引都更新一遍,为了保证原始链表中数据的有序性,我们需要先找

到要动作的位置,这个查找操作就会比较耗时最后在新增和删除的过程中的更新,时间复杂度也是O(log n)

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

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

相关文章

Cuk、Zeta和Sepic开关电源拓扑结构

Cuk、Zeta和Sepic变换器,三种拓扑结构大致类似。不同点在于电感和二极管,MOS管的位置关系的变化。 Cuk电源是一种非隔离的直流电源转换器,其基本结构包括输入滤波电容、开关管、输入电感、输出电感和输出电容等元件。Cuk电路可以看作是Boost和Buck电路的…

【CFP-专栏2】计算机类SCI优质期刊汇总(含IEEE/Top)

一、计算机区块链类SCI-IEEE 【期刊概况】IF:4.0-5.0, JCR2区,中科院2区; 【大类学科】计算机科学; 【检索情况】SCI在检; 【录用周期】3-5个月左右录用; 【截稿时间】12.31截稿; 【接收领域】区块链…

基于轻量级GhostNet模型开发构建生活场景下生活垃圾图像识别系统

轻量级识别模型在我们前面的博文中已经有过很多实践了,感兴趣的话可以自行移步阅读: 《移动端轻量级模型开发谁更胜一筹,efficientnet、mobilenetv2、mobilenetv3、ghostnet、mnasnet、shufflenetv2驾驶危险行为识别模型对比开发测试》 《基…

Dragonfly-SM X9H核心板 SM6700Q PMIC供电配置烧录介绍

一、概述 核心板采用 1 片芯迈 SM6700Q PMIC 芯片搭配 3 片 MPQ8861 DCDC 电源芯片和 2 片安森美 LDO,型号分别 NCV8164ASN180T1G 和 NCV8130BMX080TCG 为系统供电。 二、核心板供电框图 系统供电主要是 MCU 的 RTC 域、安全域、应用域的供电,其中 RTC 域…

数据库中的锁

锁 锁冲突是针对事务的,另一个事务能不能申请上锁,是和其他事务竞争。 一个事务内部可以加很多锁,互相并不会冲突。 级联回滚调度 多个事务有依赖关系,如果一个事务回滚,那么所有事务也需要回滚。 冲突的数据加锁安…

118基于matlab的二级倒立摆的数学建模

基于matlab的二级倒立摆的数学建模,引入二次型最优控制率,对系统进行仿真分析,得出摆杆及小车位置变化曲线。程序已调通,可直接运行。

计算机网络——计算大题(七)

前言: 最近也是在准备计算机考试,我们的考试形式是上机考试,所以可能有些计算题是会给提供思路的,前面已经对本学期的计算机网络知识有了一个简单的认识与了解,现在我们就来对计算大题进行一个学习吧,这里的…

【Apache Doris】自定义函数之 JAVA UDF 详解

【Apache Doris】自定义函数之 JAVA UDF 详解 一、背景说明二、原理简介三、环境信息3.1 硬件信息3.2 软件信息 四、IDE准备五、JAVA UDF开发流程5.1 源码准备5.1.1 pom.xml5.1.2 JAVA代码 5.2 mvn打包5.2.1 clean5.2.2 package 5.3 函数使用5.3.1 upload5.3.2 使用 六、注意事…

Zookeeper无法启动,报“Unable to load database on disk”

转载说明:如果您喜欢这篇文章并打算转载它,请私信作者取得授权。感谢您喜爱本文,请文明转载,谢谢。 问题描述: 测试环境部署zookeeper的服务器磁盘满了,引起服务异常,将zookeeper进程杀掉之后&…

【shell】命令行自动补全(compgen、complete、compopt)

目录 用途 小例子 说明 进阶-多级补齐 Bash自动补齐原理 用途 自编写的Shell脚本/命令,很多时候都需要输入一定的参数。当参数较多而且较复制的时候,如果能使用Tab键补全就显得非常的便利。 小例子 例如,我们自定义一个命令 footest function…

通信原理课设(gec6818) 007:语音识别

目录 1、去科大讯飞官网下载对应的sdk 2、科大讯飞文件夹的意思 3、配置ARM的录音环境 4、编程实现语音识别 我们的需求是将一个语音文件从客户端传到服务器,因此我们最好是选用tcp 现在市面上面常用的语音识别解决方案为:科大讯飞c和百度c 离…

机器学习归一化和标准化

1. 为什么做归一化和标准化 样本中有多个特征,每一个特征都有自己的定义域和取值范围,他们对距离计算也是不同的,如取值较大的影响力会盖过取值较小的参数。因此,为了公平,样本参数必须做一些归一化处理,将…

大创项目推荐 深度学习二维码识别

文章目录 0 前言2 二维码基础概念2.1 二维码介绍2.2 QRCode2.3 QRCode 特点 3 机器视觉二维码识别技术3.1 二维码的识别流程3.2 二维码定位3.3 常用的扫描方法 4 深度学习二维码识别4.1 部分关键代码 5 测试结果6 最后 0 前言 🔥 优质竞赛项目系列,今天…

springboot定时执行某个任务

springboot定时执行某个任务 要定时执行的方法加上Schedule注解 括号内跟 cron表达式 “ 30 15 10 * * ?” 代表秒 分 时 日 月 周几 启动类上加上EnableScheduling 注释

最新GPT4教程,GPT语音对话使用,Midjourney绘画,ChatFile文档对话总结+DALL-E3文生图教程工具

一、前言 ChatGPT3.5、GPT4.0、GPT语音对话、Midjourney绘画,文档对话总结DALL-E3文生图,相信对大家应该不感到陌生吧?简单来说,GPT-4技术比之前的GPT-3.5相对来说更加智能,会根据用户的要求生成多种内容甚至也可以和…

【数据结构】八、查找

一、基本概念 静态查找:只查找,不改变集合内数据元素 动态查找:有则输出元素,无则添加元素 二、静态查找表 2.1顺序查找 在线性表、链表、树中依次查找 2.2折半查找(二分查找) 在有序的线性表中&…

HTML5+CSS3+JS小实例:过年3D烟花秀

实例:过年3D烟花秀 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><…

【机器学习合集】深度生成模型 ->(个人学习记录笔记)

深度生成模型 深度生成模型基础 1. 监督学习与无监督学习 1.1 监督学习 定义 在真值标签Y的指导下&#xff0c;学习一个映射函数F&#xff0c;使得F(X)Y 判别模型 Discriminative Model&#xff0c;即判别式模型&#xff0c;又称为条件模型&#xff0c;或条件概率模型 生…

【privateGPT】使用privateGPT训练您自己的LLM

了解如何在不向提供商公开您的私人数据的情况下训练您自己的语言模型 使用OpenAI的ChatGPT等公共人工智能服务的主要担忧之一是将您的私人数据暴露给提供商的风险。对于商业用途&#xff0c;这仍然是考虑采用人工智能技术的公司最大的担忧。 很多时候&#xff0c;你想创建自己…

网大为卸任腾讯CXO;Midjourney 1 月训练视频模型;2023年马斯克赚了7700亿

投融资 • 2023 年大型科技公司在生成式 AI 初创企业上的投资远超风险投资集团• 恒信东方与无锡政府合作成立布局 MR/XR 技术及 3D 数字资产 AIGC 产业投资基金• 新公司法完善注册资本认缴登记制度• 网大为卸任腾讯CXO&#xff0c;曾促成南非MIH的投资• 宁波蔚孚科技完成数…