数据结构与算法笔记:实战篇 - 剖析Redis常用数据类型对应的数据结构

概述

从本章开始,就进入实战篇的部分。这部分主要通过一些开源醒目、经典系统,真枪实弹地教你,如何将数据结构和算法应用到项目中。所以这部分的内容,更多的是知识点的回顾,相对于基础篇和高级篇,其实这部分内容会更加容易看懂。

不过,希望你不要看懂就完了。要多举一反三,自己接触过的项目、基础框架、中间件中,都用过哪些数据结构和算法。你可以想一想,在自己做的项目中,有哪些可以用学过的数据结构和算法进一步优化。这样的学习效果才会更好。好了,本章就带你看下,经典数据库 Redis 中常用到的数据类型,底层都是用哪种数据结构和算法实现的?


Redis 数据库介绍

Redis 是一种键值(key-value)数据库。相对于关系型数据库(比如 MySQL),Redis 也被叫作非关系型数据库

关于 Redis 数据库,本人也学习过 Redis 核心技术教程,感兴趣的朋友可以去看下专栏。

像 MySQL 这样的关系型数据库,表的结构比较复杂,会包含很多字段,可以通过 SQL 语句,来实现非常复杂的查询需求。而 Redis 中只包含 “键” 和 “值” 两部分,只能通过 “键” 来查询值。真实因为这样简单的存储结构,也让 Redis 的读写效率非常高。

此外,Redis 主要是作为内存数据库来使用,也就是说,数据是存储在内存中。尽管它经常被用作内存数据库,但是它也支持将数据存储在磁盘中。这一点后面会介绍。

Redis 中,键的数据类型是字符串,但是为了丰富数据存储的方式,方便开发中使用,值的数据类型有很多,常用的数据类型有这样几种,它们分别是字符串、列表、字典、集合、有序集合。

“字符串”(String)这样的数据结构非常简单,对应到数据结构里,就是字符串。你应该非常熟悉,这里就不过多介绍了。我们着重看下其他四种比较复杂点的数据类型,看看它们底层都依赖了哪些数据结构。

列表(list)

我们先看列表。列表这种数据类型支持存储一组数据。这种数据类型对应两种实现方式,一种是压缩列表(ziplist),另一种是双向循环链表

当列表中的数据量比较小的时候,列表可以采用压缩列表的方式实现。具体需要同时满足下面两个条件:

  • 列表中保存的单个数据(有可能是字符串类型的)小于 64 字节。
  • 列表中的数据个数小于 512 个。

关于压缩列表,这里稍微解释一下。它并不是基础数据结构,而是 Redis 自己设计的一种数据存储结构。它有点类似数组,通过一片连续的内存空间,来存储数据。不过,它跟数组不同的一点是,它允许存储的数据大小不同。具体的存储结构也非常简单,你看下下面这幅图。

在这里插入图片描述

现在,我们来看看,压缩列表中的 “压缩” 两个字该如何理解?

听到 “压缩” 两个字,直观的反应是节省内存。之所以说这种存储结构节省内存,是相较于数组的存储思路而言。我们知道,数组要求每个元素的大小相同,如果我们要存储不同长度的字符串,那我们就需要用最大长度的字符串大小作为元素的大小(假设是 20 个字节)。当我们存储小于 20 个字节长度的字符串时,便会浪费部分存储空间。

在这里插入图片描述

压缩列表这种存储结构,一方面是比较节省空间,另一方面可以支持不同类型数据的存储。而且,因为数据存储在一片连续的空间,通过键来获取值为列表类型的数据,读取的效率也非常高。

当列表中存储的数据量比较大的时候,也就是不能同时满足刚刚讲的两个条件时,列表就要通过双向链表来实现了。

在链表章节中,我们已经讲过双向链表这种数据结构了。这里我们着重看一下 Redis 中双向链表的编码实现方式。

Redis 的这种双向链表的实现方式,非常值得借鉴。它额外定义一个 list 结构体,来组织链表的首、尾指针,还有长度等信息。这样,在使用的时候就会非常方便。

// 以下是C语言代码,因为Redis是C语音实现的
typedef struct listnode {struct listnode *prev;struct listnode *next;void *value;
} listnode;typedef struct list {listnode *head;listnode *tail;unsigned long len;// ...省略其他定义
} list;

字典(hash)

字典类型用来存储一组数据对。每个数据对又包含键值两部分。字典类型也有两种实现方式,一种就是刚刚讲到的压缩列表,另一种是散列表

同样,只有当存储的数据量比较小的情况下,Redis 才使用散列表来实现字典类型。具体要满足两个条件:

  • 字典中保存的键和值的大小都要小于 64 字节。
  • 字典中键值对的数量小于 512 个。

当不能同时满足上面两个条件时,Redis 就使用散列表来实现字典类型。Redis 使用 MurmurHas2 这种运行速度快、随机性好的哈希算法作为哈希函数。对于哈希冲突,Redis 采用链表法来解决。此外,Redis 还支持散列表的动态扩容、缩容。

当数据动态增加之后,散列表的装载因子会不停地变大。为了避免散列表性能的下降,当装载因子大于 1 的时候,Redis 会触发扩容,将散列表扩大为原来大小的 2 倍左右(具体值需要计算才能得到,如果感兴趣,你可以去阅读源码)。

当数据动态减少之后,为了节省内存,当装载因子小于 0.1 的时候,Redis 就会触发缩容,缩小为字典中数据个数的大约 2 倍大小(这个值也是计算得到的,如果感兴趣,可以去阅读源码)。

前面讲过,扩容缩容要做大量的数据搬移和哈希值的重新计算,所以比较耗时。针对这个问题,Redis 使用我们在散列表(中)讲的渐进式扩容缩容策略,将数据的搬移分批进行,避免了大类数据一次性搬移导致的服务停顿。

集合(set)

集合这种数据类型用来存储一组不重复的数据。这种数据类型也有两种实现方式,一种是基于有序数组,另一种是基于散列表

当要存储的数据,同时满足下面两个条件时,Redis 就采用有序数组,来实现集合这种数据类型。

  • 存储的数据都是整数。
  • 存储的数据元素个数不超 512 个。

当不能同时满足这两个条件的时候,Redis 就使用散列表来存储集合中的数据。

有序集合(sortedset)

有序集合这种数据类型,我们在跳表章节已经讲过了。它用来存储一组数据,并且每个数据会附带一个得分。通过得分的大小,我们将数据组织成跳表这样的数据结构,以及支持快速地按照得分值、得分区间获取数据。

实际上,跟 Redis 的其他数据类型一样,有序集合也并不仅仅只有跳表这一组实现方式。当数据量比较小的时候,Redis 会用压缩列表来实现有序集合。具体点说就是,使用压缩列表来实现有序集合的前提,有这样两个:

  • 所有数据的大小都要小于 64 字节。
  • 元素个数要小于 128 个。

数据结构持久化

尽管 Redis 经常不会被用作内存数据库,但是它也支持数据落盘,也就是将内存中的数据存储到磁盘中。这样,当机器断电时,存储在 Redis 中的数据也不会丢失。在机器重启之后,Redis 只需要再将存储在硬盘中的数据,重新读取到内存,就可以继续工作了。

刚刚我们讲到,Redis 的数据格式由 “键” 和 “值 两部分组成。而 “值” 又支持很多数据类型,比如字符串、列表、字典、集合、有序集合。像字典、集合等类型,底层用到了散列表,散列表中有指针的概念,而指针指向的是内存中的存储地址。那 Redis 是如何将这样一个跟具体内存有关的数据结果存储到磁盘中的呢?

实际上,Redis 遇到的这个问题并不特殊,很多场景都会遇到。我们把它叫做数据结构持久化问题,或者对象持久化问题。这里的持久化,你可以笼统地理解为 “存储到磁盘”。

如何将数据结构持久化到硬盘?我们主要有两种解决思路。

第一种是清楚原有的存储结构,只将数据存储到磁盘中。当我们需要从磁盘还原数据到内存时,再重新将数据组织称原来的数据结构。实际上,Redis 采用的就是这种持久化思路。

不过,这种方式也有一定的弊端。那就是数据从磁盘还原到内存的过程,会耗费比较多的时间。比如,我们现在要将散列表中的数据存储到磁盘。当我们从磁盘中,取出数据重新构建散列表的时候,需要重新计算每个数据的哈希值。如果磁盘中存储的是几 GB 的数据,那重构数据结构的好事就不可忽视了。

第二种方式是保留原来的存储格式,将数据按照原有的个数存储在磁盘中。我们拿散列表这样的数据结构来举例。我们可以将散列表的大小、每个数据被散列到的槽的编号等信息,都保存在磁盘中。有了这些信息,我们从磁盘中奖数据还原到内存时,就可以避免重新计算哈希值。

总结

本章,我们学习了 Redis 中常用的数据类型底层依赖的数据结构,总结一下大概有这 5 种:压缩列表(可以看做一种特殊的数组)、有序数组链表散列表跳表。实际上,Redis 就是这些常用数据结构的封装。

你有没有发现,有了数据结构和算法的基础之后,再去阅读 Redis 的源码,理解起来就容易很多了?很多原来觉得很深奥的设计思想,是不是就都会觉得顺理成章了呢?

还是那句话,夯实基础很重要。通用是看源码,有些人只能看个热闹,了解一些皮毛,无法形成自己的知识结构,不能化为己用,过不了几天就忘了。而有些人基础很好,不但知其然,还能知其所以然,从而真正理解作者设计的动机。这样不但能有助于我们理解所用的开源软件,还能为我们自己的创新添砖加瓦。

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

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

相关文章

fastapi swagger在线接口文档报错

fastapi swagger在线接口文档报错 1、报错信息 Unable to render this definition The provided definition does not specify a valid version field. Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: “2.0” and those …

【收藏】SaaS运营方法论:寻找合适的合作伙伴的四大方法

一、使用关键字研究工具查找您所在行业的相关博客、频道和网站 但是,根据你的业务规模和性质,如果你需要主动出击寻找合适的推广伙伴,而不仅限于让潜在合作伙伴找你,你可以使用关键字研究工具。 实话实说,最好的联盟营…

告别手工录入,企业财务凭证同步迈入智能新时代!

一、客户介绍 某金融租赁股份有限公司作为一家领先的金融租赁企业,一直秉持着创新驱动、服务至上的经营理念。随着业务的快速发展,该公司在财务管理和凭证管理方面遇到了新的挑战。为了更好地提升工作效率,降低运营成本,该公司决…

Spring两大核心思想 IoC和AoP

目录 ✨ 一、什么是IoC 1、定义 🎊 2、IoC思想 🎊 3、优势 🎊 4、对象的管理 🎊 存对象:Component 取对象:AutoWired ✨二、什么是DI 1、定义 🎊 2、IoC和DI的关系🎊 可…

嵌入式Linux系统编程 — 5.7 Linux系统中proc文件系统

目录​​​​​​​ 1 proc文件系统简介 2 proc 文件系统的使用 2.1 使用 cat 命令读取 2.2 使用 read()函数读取 1 proc文件系统简介 /proc 文件系统,也称为进程信息文件系统(Process Information file system),是一种在 Li…

Windows电脑自建我的世界MC服务器并与好友远程联机游戏教程

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

08:结构体

结构体 1、为什么需要结构体2、如何定义结构体3、怎么使用结构体变量3.1、赋值和初始化3.2、结构体变量的输出 1、为什么需要结构体 为了表示一些复杂的事物,而普通的基本类型无法满足实际要求。什么叫结构体 把一些基本类型数据组合在一起形成的一个新的数据类型&…

高性能全局内存池

什么时候使用高性能全局内存池? 1.高并发下有些资源需要被频繁创建和销毁,我们都知道系统调用是很消耗资源的。因此,内存池是一块申请好的资源放在缓存里。 2.频繁使用时增加了系统内存的碎片,降低内存使用效率。内存分配必须起…

11083 旅游背包(优先做)

这个问题可以使用动态规划来解决。我们可以定义一个三维数组dp,其中dp[i][j][k]表示前i种物品,总体积不超过j,总重量不超过k的最大价值。 我们可以使用四重循环来填充这个数组。外层循环遍历所有的物品,第二层循环遍历所有可能的…

2024软件设计师笔记之考点版(一考就过):考试前一天 考点记忆版

软件设计师之一考就过:成绩版 1、栈区:函数调用和返回,由系统控制;存非静态局部变量;用栈实现嵌套调用(递归调用),逆波兰式业绩也叫后缀式,用栈进行求值 2、堆区&#x…

Big Data Tools插件

一些介绍 在Jetbrains的产品中,均可以安装插件,其中:Big Data Tools插件可以帮助我们方便的操作HDFS,比如 IntelliJ IDEA(Java IDE) PyCharm(Python IDE) DataGrip(SQL …

【RabbitMQ问题踩坑】RabbitMQ设置手动ack后,消息队列有多条消息,只能消费一条,就不继续消费了,这是为什么 ?

现象:我发送5条消息到MQ队列中,同时,我在yml中设置的是需要在代码中手动确认,但是我把代码中的手动ack给关闭了,会出现什么情况? yml中配置,配置需要在代码中手动去确认消费者消费消息成功&…

教学技能大赛包括哪些内容

在教育的广阔天地里,教师的角色至关重要。他们不仅是知识的传递者,更是学生心灵成长的引导者。那么,当教师们聚集一堂,参加一场教学技能大赛时,会有哪些内容成为他们展示自我、互相学习的舞台呢? 教学技能大…

瓦罗兰特报错57/error code59/报错903的解决办法

《无畏契约》在注重产品设计之预,也注重世界观、英雄性格的塑造,里面的英雄灵感来源于不同的国家和地区,有非常本土化的特色,每个英雄的性格人设非常鲜明。这些人物特色让这款游戏有了新的玩法。很多玩家都进入到游戏体验。然后有…

SeeSR: Towards Semantics-Aware Real-World Image Super-Resolution

CVPR2024 香港理工大学&OPPO&bytedancehttps://github.com/cswry/SeeSR?tabreadme-ov-file#-licensehttps://arxiv.org/pdf/2311.16518#page5.80 问题引入 因为有些LR退化情况比较严重,所以超分之后的结果会出现语义的不一致的情况,所以本文训…

Python入门 2024/7/1

目录 第一个程序hello world 数据类型 注释 变量 用type类型查看数据类型 ​编辑 数据类型转换 ​编辑 标识符 运算符 字符串的三种定义方式 字符串拼接 ​编辑​编辑 字符串格式化 第一个程序hello world 区分c和python c是printf python是print print("h…

晶振在硬件系统中的位置选择与优化策略

在现代电子设备中,晶振扮演着至关重要的角色,它们提供稳定且精确的时钟信号,是系统心脏般的存在。然而,晶振的性能不仅取决于其本身的质量,还与它在硬件系统中的位置选择紧密相关。一个恰当的位置能够最大限度地减少外…

基于K线最短路径构造的非流动性因子

下载地址https://download.csdn.net/download/SuiZuoZhuLiu/89492221

nodejs--【Express基本使用】

10 【Express基本使用】 https://www.expressjs.com.cn/ 基于 Node.js 平台,快速、开放、极简的 web 开发框架。 1.Express的安装方式 Express的安装可直接使用npm包管理器上的项目,在安装npm之前可先安装淘宝镜像: npm install -g cnpm -…

领先Intel 旗舰60%,AMD锐龙9000系桌面CPU彻底杀疯了

早在月初台北国际电脑展上,Intel 公布了下一代低功耗移动端处理器 Lunar Lake。 也就是第二代移动版酷睿 Ultra。 而作为叫板王,AMD 丝毫不怂,不但掏出了 Ryzen AI 300 移动端处理器应对。 还抢在 Intel 之前带来了全新一代 Zen 5 架构 Ryz…