数据结构与算法笔记:实战篇 - 剖析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.rhkb.cn/news/365663.html

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

相关文章

短视频电商源码如何选择

在数字时代的浪潮下,短视频电商以其直观、生动、互动性强的特点,迅速崛起成为电商行业的一股新势力。对于有志于进军短视频电商领域的创业者来说,选择一款合适的短视频电商源码至关重要。本文将从多个角度探讨如何选择短视频电商源码&#xf…

【每日刷题】Day78

【每日刷题】Day78 🥕个人主页:开敲🍉 🔥所属专栏:每日刷题🍍 🌼文章目录🌼 1. 1608. 特殊数组的特征值 - 力扣(LeetCode) 2. 1385. 两个数组间的距离值 - …

我的世界服务器-高版本服务器-MC服务器-生存服务器-RPG服务器-幻世星辰

生存为主,RPG乐趣为辅,重视每位玩家的建议,一起打造心目中的服务器,与小伙伴一起探险我的世界! 服务器版本: 1.18.2 ~ 1.20.4 Q群: 338238381 服务器官网: 星辰毛毛雨-Minecraft高版本生存服务器我的世界…

JVM原理(八):JVM虚拟机工具之基础故障工具

这里主要介绍监视虚拟机运行状态和进行故障处理的工具 1. jsp:虚拟机进程状况工具 jsp命令格式: jsp [options] [hostid] jps远程查询虚拟机进程状态 2. jstat:虚拟机统计信息监视工具 jstat命令格式: jstat [option vmid [interval [s|ms] [count]…

计算机专业课面试常见问题-计算机网络篇

目录 1. 计算机网络分为哪 5 层? 2. TCP 协议简述? 3. TCP 和 UDP 的区别?->不同的应用场景? 4. 从浏览器输入网址到显示页…

Wireshark - tshark支持iptables提供数据包

tshark现在的数据包获取方式有两种,分别是读文件、网口监听(af-packet原始套接字)。两种方式在包获取上,都是通过读文件的形式;存在文件io操作,在专门处理大流量的情境下, 我们复用wireshark去做…

【mysql死锁】示例 和讨论 “SHOW ENGINE INNODB STATUS“

文章目录 mysql 死锁死锁演示表结构如下 死锁查询mysql 详情命令行 SHOW ENGINE INNODB STATUS 如果 两个事务都是按照先更新1 再更新2的顺序去做更新 会发生死锁么?验证一下所以 如果顺序是一致的 不会产生死锁 只会进行等待 防止mysql 死锁的方式优化sql 自行顺序…

2024超全盘点:5大文档加密系统,文档加密系统都有哪些功能

在众多文档加密系统中,以下是五款备受推崇的软件,其中包括安企神软件、加密精灵等,它们各自具备独特的优势和特点,以满足不同企业对数据安全的需求。 1. 安企神软件 特点与优势:安企神软件以其全面的数据保护功能见长…

C语言从入门到进阶(15万字总结)

前言: 《C语言从入门到进阶》这本书可是作者呕心沥血之作,建议零售价1元,当然这里开个玩笑。 本篇博客可是作者之前写的所有C语言笔记博客的集结,本篇博客不止有知识点,还有一部分代码练习。 有人可能会问&#xff…

基于STM32的简易智能家居设计

一、项目功能概述 1、OLED显示温湿度、空气质量,并可以设置报警阈值 2、设置4个继电器开关,分别控制灯、空调、开关、风扇 3、设计一个离线语音识别系统,可以语音控制打开指定开关、并且可以显示识别命令词到OLED屏上 4、OLED实时显示&#…

idea启用多个环境

背景 在平常的后端开发中,需要与前端联调,比较方便的是让前端直接连自己的本地环境(毕竟每次都要打包部署到测试环境实在是太麻烦了)。但是这样子也有点不好,就是自己功能还没写好呢,结果前端连着自己的环…

微信小程序template模板引入

如图:temp.wxml是template引入的模板 在two.wxml中: import:是引入temp的页面让template中的内容显示出来在two页面中; include:是显示temp页面内容不在template包裹,template以外的view标签文字和不在view的文字让…

【Linux】进程信号_2

文章目录 八、进程信号1. 信号 未完待续 八、进程信号 1. 信号 除了可以使用 kill 命令和键盘来生成信号,我们也可以使用系统调用来生成信号。 kill函数可以对指定进程发送指定信号。 使用方法: int main(int argc, char *argv[]) {if (argc ! 3) {c…

6-14题连接 - 高频 SQL 50 题基础版

目录 1. 相关知识点2. 例子2.6. 使用唯一标识码替换员工ID2.7- 产品销售分析 I2.8 - 进店却未进行过交易的顾客2.9 - 上升的温度2.10 - 每台机器的进程平均运行时间2.11- 员工奖金2.12-学生们参加各科测试的次数2.13-至少有5名直接下属的经理2.14 - 确认率 1. 相关知识点 left …

美国服务器租用详细介绍与租用流程

在数字化时代,服务器租用已成为许多企业和个人拓展业务、存储数据的重要选择。美国作为全球科技发展的前沿阵地,其服务器租用服务也备受瞩目。下面,我们将详细介绍美国服务器租用的相关知识及租用流程。 一、美国服务器租用简介 美国服务器租…

【K8s】专题六(3):Kubernetes 稳定性之自动扩缩容

以下内容均来自个人笔记并重新梳理,如有错误欢迎指正!如果对您有帮助,烦请点赞、关注、转发!欢迎扫码关注个人公众号! 一、基本介绍 在 Kubernetes 中,自动扩缩容是一种动态调整集群资源,以灵活…

前端vue项目升级nodejs后无法运行了

问题描述: 运行、打包都正常的vue项目,在将nodejs升级到v20.14.0后,均报错了: Error: error:0308010C:digital envelope routines::unsupported opensslErrorStack: [ error:03000086:digital envelope routines::initializ…

Java高级重点知识点-17-异常

文章目录 异常异常处理自定义异常 异常 指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。Java处 理异常的方式是中断处理。 异常体系 异常的根类是 java.lang.Throwable,,其下有两个子类:ja…

# Kafka_深入探秘者(5):kafka 分区

Kafka_深入探秘者(5):kafka 分区 一、kafka 副本机制 1、Kafka 可以将主题划分为多个分区(Partition),会根据分区规则选择把消息存储到哪个分区中,只要如果分区规则设置的合理,那么所有的消息将会被均匀的…

FastDFS部署

版本介绍 安装fastdfs共需要俩个安装包 fastdfs-5.05.tar.gz libfastcommon-1.0.7.tar.gz编译安装 libfastcommon tar -xvf libfastcommon-1.0.7.tar.gz cd libfastcommon-1.0.7 make.sh make.sh install 3. 设置软链接 libfastcommon.so默认安装到了/usr/lib64/libfastcommon.…