Redis | 数据结构(02)SDS

news2024/11/20 20:28:41

一、键值对数据库是怎么实现的?

在开始讲数据结构之前,先给介绍下 Redis 是怎样实现键值对(key-value)数据库的。
Redis 的键值对中的 key 就是字符串对象,而 value 可以是字符串对象,也可以是集合数据类型的对象,比如 List 对象、Hash 对象、Set 对象和 Zset 对象。
举个例子,我这里列出几种 Redis 新增键值对的命令:

> SET name "mingjing"
OK
> HSET person name "mingjing" age 18
0
> RPUSH stu "mingjing" "huahua"
(integer) 

这些命令代表着:
● 第一条命令:name 是一个字符串键,因为键的值是一个字符串对象;
● 第二条命令:person 是一个哈希表键,因为键的值是一个包含两个键值对的哈希表对象;
● 第三条命令:stu 是一个列表键,因为键的值是一个包含两个元素的列表对象;

1.1 这些键值对是如何保存在 Redis 中的呢?

Redis 是使用了一个「哈希表」保存所有键值对,哈希表的最大好处就是让我们可以用 O(1) 的时间复杂度来快速查找到键值对。哈希表其实就是一个数组,数组中的元素叫做哈希桶

1.2 Redis 的哈希桶是怎么保存键值对数据的呢?

哈希桶存放的是指向键值对数据的指针(dictEntry*),这样通过指针就能找到键值对数据,然后因为键值对的值可以保存字符串对象和集合数据类型的对象,所以键值对的数据结构中并不是直接保存值本身,而是保存了 void * keyvoid * value 指针,分别指向了实际的键对象和值对象,这样一来,即使值是集合数据,也可以通过 void * value 指针找到。如下图:
在这里插入图片描述

这些数据结构的内部细节,我先不展开讲,后面在讲哈希表数据结构的时候,在详细的说说,因为用到的数据结构是一样的。这里先大概说下图中涉及到的数据结构的名字和用途:
● redisDb 结构,表示 Redis 数据库的结构,结构体里存放了指向了 dict 结构的指针;
● dict 结构,结构体里存放了 2 个哈希表,正常情况下都是用「哈希表1」,「哈希表2」只有在 rehash 的时候才用,具体什么是 rehash,我在本文的哈希表数据结构会讲;
● ditctht 结构,表示哈希表的结构,结构里存放了哈希表数组,数组中的每个元素都是指向一个哈希表节点结构(dictEntry)的指针;
● dictEntry 结构,表示哈希表节点的结构,结构里存放了 void * keyvoid * value 指针, key 指向的是 String 对象,而 value 则可以指向 String 对象,也可以指向集合类型的对象,比如 List 对象、Hash 对象、Set 对象和 Zset 对象。
特别说明下,void * keyvoid * value 指针指向的是 Redis 对象,Redis 中的每个对象都由 redisObject 结构表示,如下图:
在这里插入图片描述

对象结构里包含的成员变量:
● type,标识该对象是什么类型的对象(String 对象、 List 对象、Hash 对象、Set 对象和 Zset 对象);
● encoding,标识该对象使用了哪种底层的数据结构;
● ptr,指向底层数据结构的指针。
所以Redis 键值对数据库的全景图如下,你就能清晰知道 Redis 对象和数据结构的关系了:
在这里插入图片描述

接下里,就好好聊一下底层数据结构!

二、SDS简洁

字符串在 Redis 中是很常用的,键值对中的键是字符串类型,值有时也是字符串类型。
Redis 是用 C 语言实现的,但是它没有直接使用 C 语言的 char 字符数组来实现字符串,而是自己封装了一个名为简单动态字符串(simple dynamic string,SDS) 的数据结构来表示字符串,也就是 Redis 的 String 数据类型的底层数据结构是 SDS。
既然 Redis 设计了 SDS 结构来表示字符串,肯定是 C 语言的 char 字符数组存在一些缺陷。
要了解这一点,得先来看看 char 字符数组的结构。

2.1 Redis为什么不直接使用C 语言字符串

C 语言的字符串其实就是一个字符数组,即数组中每个元素是字符串中的一个字符。
比如,下图就是字符串“mingjing”的 char 字符数组的结构:
char* name = “mingjing”

mingjing\0

在 C 语言里,对字符串操作时,char * 指针只是指向字符数组的起始位置,而字符数组的结尾位置就用“0”表示,意思是指字符串的结束。
因此,C 语言标准库中的字符串操作函数就通过判断字符是不是 “0” 来决定要不要停止操作,如果当前字符不是 “0” ,说明字符串还没结束,可以继续操作,如果当前字符是 “0” 是则说明字符串结束了,就要停止操作。

C 语言字符串用 “0” 字符作为结尾标记有个缺陷。假设有个字符串中有个 “0” 字符,这时在操作这个字符串时就会提早结束,

因此,除了字符串的末尾之外,字符串里面不能含有 “0” 字符,否则最先被程序读入的 “0” 字符将被误认为是字符串结尾,这个限制使得 C 语言的字符串只能保存文本数据,不能保存像图片、音频、视频文化这样的二进制数据(这也是一个可以改进的地方)
另外, C 语言标准库中字符串的操作函数是很不安全的,对程序员很不友好,稍微一不注意,就会导致缓冲区溢出。
举个例子,strcat 函数是可以将两个字符串拼接在一起。

//将 src 字符串拼接到 dest 字符串后面
char*strcat(char*dest,constchar* src);

C 语言的字符串是不会记录自身的缓冲区大小的,所以 strcat 函数假定程序员在执行这个函数时,已经为 dest 分配了足够多的内存,可以容纳 src 字符串中的所有内容,而一旦这个假定不成立,就会发生缓冲区溢出将可能会造成程序运行终止,(这是一个可以改进的地方)。
而且,strcat 函数和 strlen 函数类似,时间复杂度也很高,也都需要先通过遍历字符串才能得到目标字符串的末尾。然后对于 strcat 函数来说,还要再遍历源字符串才能完成追加,对字符串的操作效率不高。
好了, 通过以上的分析,我们可以得知 C 语言的字符串不足之处以及可以改进的地方:
● 获取字符串长度的时间复杂度为 O(N);
● 字符串的结尾是以 “0” 字符标识,字符串里面不能包含有 “0” 字符,因此不能保存二进制数据;
● 字符串操作函数不高效且不安全,比如有缓冲区溢出的风险,有可能会造成程序运行终止;
Redis 实现的 SDS 的结构就把上面这些问题解决了,接下来我们一起看看 Redis 是如何解决的。

三、 SDS 结构设计

下图就是 Redis 5.0 的 SDS 的数据结构:
在这里插入图片描述

结构中的每个成员变量分别介绍下:
● len,记录了字符串长度。这样获取字符串长度的时候,只需要返回这个成员变量值就行,时间复杂度只需要 O(1)。
● alloc,分配给字符数组的空间长度。这样在修改字符串的时候,可以通过 alloc - len 计算出剩余的空间大小,可以用来判断空间是否满足修改需求,如果不满足的话,就会自动将 SDS 的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用 SDS 既不需要手动修改 SDS 的空间大小,也不会出现前面所说的缓冲区溢出的问题。
● flags,用来表示不同类型的 SDS。一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64,后面在说明区别之处。
● buf[],字符数组,用来保存实际数据。不仅可以保存字符串,也可以保存二进制数据。
总的来说,Redis 的 SDS 结构在原本字符数组之上,增加了三个元数据:len、alloc、flags,用来解决 C 语言字符串的缺陷。

3.1 O(1)复杂度获取字符串长度

C 语言的字符串长度获取 strlen 函数,需要通过遍历的方式来统计字符串长度,时间复杂度是 O(N)。
而 Redis 的 SDS 结构因为加入了 len 成员变量,那么获取字符串长度的时候,直接返回这个成员变量的值就行,所以复杂度只有 O(1)。

3.2 二进制安全

因为 SDS 不需要用 “0” 字符来标识字符串结尾了,而是有个专门的 len 成员变量来记录长度,所以可存储包含 “0” 的数据。但是 SDS 为了兼容部分 C 语言标准库的函数, SDS 字符串结尾还是会加上 “0” 字符。
因此, SDS 的 API 都是以处理二进制的方式来处理 SDS 存放在 buf[] 里的数据,程序不会对其中的数据做任何限制,数据写入的时候时什么样的,它被读取时就是什么样的。
通过使用二进制安全的 SDS,而不是 C 字符串,使得 Redis 不仅可以保存文本数据,也可以保存任意格式的二进制数据。

3.3 不会发生缓冲区溢出

C 语言的字符串标准库提供的字符串操作函数,大多数(比如 strcat 追加字符串函数)都是不安全的,因为这些函数把缓冲区大小是否满足操作需求的工作交由开发者来保证,程序内部并不会判断缓冲区大小是否足够用,当发生了缓冲区溢出就有可能造成程序异常结束。
所以,Redis 的 SDS 结构里引入了 alloc 和 len 成员变量,这样 SDS API 通过 alloc - len 计算,可以算出剩余可用的空间大小,这样在对字符串做修改操作的时候,就可以由程序内部判断缓冲区大小是否足够用。
而且,当判断出缓冲区大小不够用时,Redis 会自动将扩大 SDS 的空间大小,以满足修改所需的大小。
SDS 扩容的规则代码如下:

hisds hi_sdsMakeRoomFor(hisds s,size_t addlen)
{
	......
	// s目前的剩余空间已足够,无需扩展,直接返回
	if(avail >= addlen)
	return s;
	//获取目前s的长度
	    len =hi_sdslen(s);
	    sh =(char*)s -hi_sdsHdrSize(oldtype);
	//扩展之后 s 至少需要的长度
	    newlen =(len + addlen);
	//根据新长度,为s分配新空间所需要的大小
	if(newlen < HI_SDS_MAX_PREALLOC)
	//新长度<HI_SDS_MAX_PREALLOC 则分配所需空间*2的空间
	        newlen *=2;
	else
	//否则,分配长度为目前长度+1MB
	        newlen += HI_SDS_MAX_PREALLOC;
	...
}

● 如果所需的 sds 长度小于 1 MB,那么最后的扩容是按照翻倍扩容来执行的,即 2 倍的newlen
● 如果所需的 sds 长度超过 1 MB,那么最后的扩容长度应该是 newlen + 1MB。
在扩容 SDS 空间之前,SDS API 会优先检查未使用空间是否足够,如果不够的话,API 不仅会为 SDS 分配修改所必须要的空间,还会给 SDS 分配额外的「未使用空间」。
这样的好处是,下次在操作 SDS 时,如果 SDS 空间够的话,API 就会直接使用「未使用空间」,而无须执行内存分配,有效的减少内存分配次数。
所以,使用 SDS 即不需要手动修改 SDS 的空间大小,也不会出现缓冲区溢出的问题。

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

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

相关文章

创建并启动华为HarmonyOS本地与远程模拟器及远程真机

1.打开设备管理器 2.选择要添加的手机设备,然后点击安装 3.正在下载华为手机模拟器 4.下载完成 5.创建新模拟器 下载系统镜像 点击下一步,创建模拟器 创建成功 启动模拟器 华为模拟器启动成功 6.登陆华为账号并使用远程模拟器 7.使用远程真机

论环境如何影响我们的一切

本心、输入输出、结果 文章目录 论环境如何影响我们的一切前言相关书籍极大影响我们的潜在客观环境环境带给我们的影响是如何进行具象化的大的框架体系是什么弘扬爱国精神论环境如何影响我们的一切 编辑:简简单单 Online zuozuo 地址:https://blog.csdn.net/qq_15071263 如果…

每日一练——返回链表的中间结点

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…

数据库扫描工具scuba

目录: Scuba的简单介绍 Scuba的安装 :Windows安装 、Linux安装 Scuba的扫描及结果分析 一、什么是Scuba? Scuba是一款网络安全扫描工具,而非数据库扫描软件。Scuba支持对Web应用程序、操作系统、网络协议等进行扫描,以发现潜在的安全漏洞和风险,并提供详细的扫描结果报告…

VM及WindowsServer安装

目录 一.操作系统的简介及常用的操作系统 二.windows的安装 安装VMWare虚拟机 注意点一 ​编辑 注意点二 三.安装配置Windows Server 2012 R2 四、虚拟机的环境配置及连接 1. 主机连接虚拟机 2. 虚拟机环境配置及共享 3. 环境配置 一.操作系统的简介及常用的操作系…

心理咨询医院预约和挂号系统

源码下载地址 支持&#xff1a;远程部署/安装/调试、讲解、二次开发/修改/定制 系统分为&#xff1a;患者端、医生端、管理员端。 患者端 医生端 管理员端

基本的设备使用

一.微步的态势感知 经常使用的 威胁---实时监控 里面有重要信息 里面有威胁名称 还有攻击类型 威胁--告警主机 查看主机威胁 webshell 查看打到webshell 调查---攻击者分析 平台管理 ---审计日志 2.防火墙 华为 usg6000 策略---黑名单 风塔防火墙…

JVM相关面试题(每日一练)

1. 什么是垃圾回收机制&#xff1f; 垃圾收集 Garbage Collection 通常被称为“GC”&#xff0c;它诞生于1960年 MIT 的 Lisp 语言&#xff0c;经过半个多世纪&#xff0c;目前已经十分成熟了。 jvm 中&#xff0c;程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭&a…

深度解读MediaBox SDKs如何实现技术架构升级

本专栏将分享阿里云视频云MediaBox系列技术文章&#xff0c;深度剖析音视频开发利器的技术架构、技术性能、开发能效和最佳实践&#xff0c;一起开启音视频的开发之旅。本文为MediaBox技术架构篇&#xff0c;重点从音视频终端SDK的技术架构、优化设计、架构优势等方面&#xff…

卡巴斯基8(2009)杀毒软件

下载地址&#xff1a;https://user.qzone.qq.com/512526231/main https://user.qzone.qq.com/3503787372/main

简单8位CPU设计verilog微处理器,源码/视频

名称&#xff1a;8位CPU设计微处理器 软件&#xff1a;QuartusII 语言&#xff1a;Verilog 代码功能&#xff1a; 设计一个简单的处理器&#xff0c;可以实现加减法以及简单的逻辑运算。 设计包括程序计数器电路&#xff0c;指令存储器电路&#xff0c;指令译码器电路(控制器…

redis 配置主从复制,哨兵模式案例

哨兵(Sentinel)模式 1 . 什么是哨兵模式&#xff1f; 反客为主的自动版&#xff0c;能够自动监控master是否发生故障&#xff0c;如果故障了会根据投票数从slave中挑选一个 作为master&#xff0c;其他的slave会自动转向同步新的master&#xff0c;实现故障自动转义 2 . 原理…

(自适应手机端)生活家具产品网站模板下载-带三级子菜单栏目

(自适应手机端)生活家具产品网站模板下载 PbootCMS内核开发的网站模板&#xff0c;该模板适用于生活家具产品网站等企业&#xff0c;当然其他行业也可以做&#xff0c;只需要把文字图片换成其他行业的即可&#xff1b; 自适应手机端&#xff0c;同一个后台&#xff0c;数据即时…

JavaWeb——IDEA操作:Project最终新建module

在project中创建新的module&#xff1a; 创建一个新的module很容易&#xff0c;但是它可能连接不上Tomcat&#xff0c;因此需要修改一些配置&#xff1a; 将以下地址修改为新module的地址

ElasticSearch(ES)8.1及Kibana在docker环境下如何安装

ES基本信息介绍 Elasticsearch&#xff08;简称ES&#xff09;是一个开源的分布式搜索和分析引擎&#xff0c;最初由Elastic公司创建。它属于Elastic Stack&#xff08;ELK Stack&#xff09;的核心组件之一&#xff0c;用于实时地存储、检索和分析大量数据。 以下是Elastics…

八大排序算法(C语言版)之插入排序

八大排序详解 目录&#xff1a;一、排序的概念1.1 排序的概念1.2 排序的应用 二、直接插入排序三、希尔排序四、排序算法复杂度及稳定性分析 目录&#xff1a; 八大排序算法&#xff1a; #mermaid-svg-7qCaGEYz0Jyj9dYw {font-family:"trebuchet ms",verdana,arial,…

Win安装protobuf和IDEA使用protobuf插件

一、Win安装protobuf 1、下载编译器 protobuf下载地址&#xff1a;https://github.com/protocolbuffers/protobuf/releases 选择自己需要的版本下载&#xff0c;这里下载的是 protoc-3.19.1-win64.zip&#xff0c;下载之后进行解压即可。 2、配置环境变量 path 系统变量中添加…

用IntelliJ远程打断点调试

前提当然是本地和远程部署的代码一样。 记录下步骤&#xff1a; 1&#xff0c;用token登录kuboard&#xff0c;找到目标容器的IP&#xff1a; 2, 用上一步找到的IP等信息创建Remote JVM Debug: 3&#xff0c;打断点&#xff0c;wkb说要把断点此属性改为线程。我试了下似乎…

menuconfig 图形化配置原理说明二

一. 简介 之前一篇文章中&#xff0c;我们已经知道&#xff1a; scripts/kconfig/mconf 会调用 uboot 根目录下的 Kconfig 文件开始 构建图形化配置界面。 接下来简单学习一下 Kconfig 的语法。因为后面学习 Linux 驱动开发 时&#xff0c;可能会涉及到修改 Kconfig…

VNC图形化远程连接Ubuntu服务器

我的Ubuntu版本22.04.3&#xff0c;带有gnome图形桌面。配置过程参考了几篇博客&#xff0c;大致流程如下。因为是配置完之后才整理的流程&#xff0c;可能有疏漏。 Ubuntu服务器上的配置 1.先在服务器上下载vnc server&#xff08;任何一种版本均可&#xff09; vncserver有…