Redis数据结构与底层实现揭秘

news2024/10/6 20:41:18

在高并发的系统开发中,缓存和高效的数据存储机制对于提升应用性能至关重要。Redis,作为其中的佼佼者,以其卓越的性能和丰富的数据结构赢得了开发者的青睐。本文将深入探讨Redis的数据结构及其底层实现,带领读者走进这个高性能数据库的幕后世界。

一、Redis数据结构概览

Redis支持五种主要数据结构:字符串(Strings)、列表(Lists)、哈希表(Hashes)、集合(Sets)和有序集合(Sorted Sets)。这些数据结构为开发者提供了灵活的数据操作方式,满足了不同场景下的数据存储需求。

  • 字符串(Strings):最基本的数据类型,可以包含任何数据,如数字、字符串、二进制数据等。在Redis中,字符串是二进制安全的,这意味着它们可以有任何长度,并且不会因为包含空字符而被截断。
  • 列表(Lists):简单的字符串列表,按照插入顺序排序。你可以添加一个元素到头部(左边)或者尾部(右边)。
  • 哈希表(Hashes):是键值对的集合,是字符串类型的字段和值的映射表。适合存储对象。
  • 集合(Sets):是字符串类型的无序集合。它是通过哈希表实现的,可以做到添加、删除、查找的时间复杂度都是O(1)。
  • 有序集合(Sorted Sets):和Sets相似,但每个字符串元素都会关联一个浮点数类型的分数。元素的分数用来排序,如果两个成员有相同的分数,那么他们的排名按照字典序计算。
    在这里插入图片描述

二、Redis底层实现揭秘

Redis的性能优势很大程度上来自于其精巧的底层数据结构和编码方式。Redis并没有直接使用上述的高级数据结构进行存储,而是根据数据的特性和大小,选择最合适的内部编码方式。

在这里插入图片描述

1.字符串的底层实现:简单动态字符串(SDS)

Redis的字符串类型并不是直接使用C语言中的原生字符串(以空字符\0结尾的字符数组)进行存储,而是使用了一个称为简单动态字符串(Simple Dynamic String,SDS)的数据结构。这种设计选择为Redis带来了许多优势,尤其是在性能和灵活性方面。

SDS结构

SDS的数据结构定义大致如下(可能根据Redis版本有所不同):

struct sdshdr {  
    int len;      // 记录buf数组中已使用字节的数量,等于SDS所保存字符串的长度  
    int free;     // 记录buf数组中未使用字节的数量  
    char buf[];   // 字节数组,用于保存字符串。注意这里并没有指明数组的长度,这是一个柔性数组(flexible array member)  
};

在这里插入图片描述

优势分析

  • 预分配:SDS会为buf分配额外的未使用空间(通过free字段记录),这意味着当你向一个SDS字符串追加内容时,如果未使用空间足够,Redis就不需要重新分配内存。这减少了内存分配次数,从而提高了性能。

  • 常数时间复杂度获取字符串长度:由于SDS结构内部维护了一个len字段来记录字符串的当前长度,获取字符串长度的操作可以在常数时间复杂度O(1)内完成,而不需要像C语言的原生字符串那样遍历整个字符串。

  • 二进制安全:SDS可以存储任意二进制数据,包括空字符\0。C语言的原生字符串以空字符作为结束标志,这限制了它们不能包含空字符。而SDS则通过len字段来明确字符串的长度,因此不受此限制。

  • 兼容C语言字符串函数:尽管SDS提供了自己的一套API来进行字符串操作,但它的buf字段实际上就是一个普通的C字符串(以\0结尾),这意味着在必要时,可以直接使用标准的C语言字符串处理函数来操作buf字段(尽管通常不推荐这样做,因为可能会破坏SDS结构的完整性)。

操作优化

SDS提供了一组API来进行字符串的创建、修改、拼接等操作。这些API在内部会处理内存分配、长度更新等细节,使得用户在使用时无需关心底层实现。

例如,当你使用sdscat函数向一个SDS字符串追加内容时,该函数会首先检查未使用空间是否足够,如果不够,则会重新分配更大的内存空间,并将原有数据复制到新位置,然后再追加新内容。所有这些操作对用户都是透明的。

通过使用SDS作为字符串的底层实现,Redis实现了字符串操作的高效性和灵活性,为上层提供了丰富的数据操作接口,同时保证了内部数据的一致性和稳定性。这种设计使得Redis在处理大量字符串数据时能够保持出色的性能。

2.列表的底层实现:双向链表与压缩列表

Redis的列表(Lists)数据类型是一个非常重要的数据结构,它允许用户在列表的两端推入或者弹出元素。为了实现这种高效的操作,Redis的列表在底层使用了两种数据结构:双向链表和压缩列表。选择哪种结构取决于列表的大小和元素的特性。

  • 双向链表

当列表的元素数量较多或者元素较大时,Redis会选择使用双向链表作为底层实现。双向链表中的每个节点都保存了前一个节点和后一个节点的指针,这使得在列表的任何位置插入或删除元素都变得相对容易。

双向链表的结构大致如下:

typedef struct listNode {  
    struct listNode *prev;  // 指向前一个节点的指针  
    struct listNode *next;  // 指向后一个节点的指针  
    void *value;            // 节点保存的数据  
} listNode;  
  
typedef struct list {  
    listNode *head;         // 指向链表头部的指针  
    listNode *tail;         // 指向链表尾部的指针  
    unsigned long len;      // 链表的长度  
    // ... 可能还有其他字段,如复制函数、比较函数等  
} list;

使用双向链表的优势在于:

可以在O(1)时间复杂度内完成在列表头部或尾部的元素插入和删除。
当需要遍历列表时,可以从头部或尾部开始,沿着节点的指针依次访问。

  • 压缩列表

当列表的元素数量较少且元素较小时,Redis会使用压缩列表(ziplist)作为底层实现来节省内存。压缩列表是一个紧凑的、连续的内存块,它按顺序存储了列表中的元素。

压缩列表的结构大致如下:

+--------+--------+--------+------+  
| ZLBYTE | LEN    | 'one'  | 'two'| ...  
+--------+--------+--------+------+
  • ZLBYTE: 压缩列表的头部信息,包含了特殊编码和压缩列表的长度信息。
  • LEN: 每个元素前的长度字段,用于记录该元素的长度或前一个元素到当前元素的偏移量。
  • ‘one’, ‘two’: 实际的列表元素,它们被连续地存储在压缩列表中。

使用压缩列表的优势在于:

内存利用率高,因为元素是连续存储的,没有额外的指针开销。
对于小列表,操作速度可以很快,因为所有数据都在一个连续的内存块中。

操作优化

Redis的列表实现提供了一组API来进行列表的创建、修改、遍历等操作。这些API在内部会根据列表的大小和元素的特性选择合适的底层数据结构,并且在必要时进行数据结构之间的转换。

例如,当向一个使用压缩列表实现的列表中添加一个新元素时,如果添加后的列表仍然满足压缩列表的使用条件(即元素数量和大小都没有超过预设的阈值),那么Redis会直接在压缩列表的末尾添加新元素。否则,Redis会将压缩列表转换为双向链表,并在链表的尾部添加新元素。

通过使用双向链表和压缩列表作为底层实现,Redis的列表数据类型能够在不同的使用场景下提供高效的操作性能。这种灵活的设计使得Redis能够处理各种大小和复杂度的列表数据,同时保持内存的低消耗和操作的快速性。

3. 哈希的底层实现:Redis中的字典与压缩列表

Redis的哈希(Hashes)类型允许用户在单个键中存储多个字段和对应的值。为了高效地支持这种数据结构,Redis在底层使用了两种主要的数据结构来实现哈希:字典(也称为哈希表)和压缩列表。

  • 字典(哈希表

当哈希中的字段和值较多或者较大时,Redis会选择使用字典作为底层实现。字典是一种通过键(在Redis哈希中是字段)来直接访问值的数据结构,它能够在平均情况下提供O(1)时间复杂度的查找、插入和删除操作。
在这里插入图片描述

Redis的字典实现通常包含两个哈希表,用于处理哈希表扩容时的数据迁移。每个哈希表节点保存了字段的哈希值、字段本身和对应的值。结构大致如下:

typedef struct dictEntry {  
    void *key;                // 字段  
    union {  
        void *val;  
        uint64_t u64;  
        int64_t s64;  
        // ... 其他可能的值类型  
    } v;                      // 值  
    struct dictEntry *next;   // 指向下一个节点的指针,用于解决哈希冲突  
} dictEntry;  
  
typedef struct dict {  
    dictEntry **table;        // 哈希表数组  
    unsigned long size;       // 哈希表大小  
    unsigned long sizemask;   // 用于计算索引的掩码  
    unsigned long used;       // 已使用的节点数量  
    // ... 可能还有其他字段,如哈希函数、复制函数等  
} dict;

使用字典的优势在于:

提供了快速的字段查找、插入和删除操作。
哈希表的扩容机制可以保持较低的哈希冲突率,从而保证操作的效率。

  • 压缩列表

当哈希中的字段和值较少且较小时,Redis会使用压缩列表作为底层实现来节省内存。压缩列表是一种紧凑的、连续的内存块,它按顺序存储了哈希中的字段和值对。

压缩列表的结构大致如下:

+--------+--------+--------+--------+  
| ZLBYTE | LEN1   | FIELD1 | LEN2   | VALUE2 | ...  
+--------+--------+--------+--------+
ZLBYTE:压缩列表的头部信息。
LEN1、FIELD1:第一个字段的长度和字段本身。
LEN2、VALUE2:第一个字段对应的值的长度和值本身。

以此类推,后续的字段和值对也是按照这个格式存储的。

使用压缩列表的优势在于:

  • 内存利用率高,因为字段和值是连续存储的,没有额外的指针和元数据开销。
  • 对于小哈希,操作速度可以很快,因为所有数据都在一个连续的内存块中。

操作优化

Redis的哈希实现提供了一组API来进行哈希的创建、修改、查找等操作。这些API在内部会根据哈希的大小和字段的特性选择合适的底层数据结构,并且在必要时进行数据结构之间的转换。

例如,当向一个使用压缩列表实现的哈希中添加一个新的字段和值时,如果添加后的哈希仍然满足压缩列表的使用条件(即字段和值的数量和大小都没有超过预设的阈值),那么Redis会直接在压缩列表的末尾添加新的字段和值。否则,Redis会将压缩列表转换为字典,并在字典中插入新的字段和值。

通过使用字典和压缩列表作为底层实现,Redis的哈希数据类型能够在不同的使用场景下提供高效的操作性能。这种灵活的设计使得Redis能够处理各种大小和复杂度的哈希数据,同时保持内存的低消耗和操作的快速性。

4. 集合的底层实现:整数集合和字典

Redis的集合(Sets)是一个无序的、元素不重复的集合。为了高效地支持这种数据结构及其操作,Redis在底层使用了两种主要的数据结构:整数集合(intset)和字典(hashtable)。

整数集合(int set)

当集合中的元素都是整数,并且元素数量较少时,Redis会选择使用整数集合作为底层实现。整数集合是一个紧凑的数组,数组中的每个元素都是集合中的一个整数。
在这里插入图片描述

整数集合的优势在于:

  • 内存利用率高:整数集合将整数紧密地存储在一个连续的内存块中,没有额外的指针或元数据开销。
  • 操作速度快:对于整数集合中的元素,Redis可以直接通过数组索引访问,这使得查找、添加和删除整数的操作非常快速。
    然而,整数集合也有其局限性。由于它要求集合中的元素必须是整数,并且元素数量较少,因此在处理非整数元素或大量元素时,整数集合可能不是最优的选择。

字典(hashtable)

当集合中的元素不满足整数集合的条件(即元素不是整数或元素数量较多)时,Redis会使用字典作为底层实现。字典是一种哈希表,它通过哈希函数将元素的哈希值映射到相应的桶(bucket)中,以支持快速的查找、插入和删除操作。

字典的优势在于:

  • 灵活性高:字典可以存储任意类型的元素,而不仅仅是整数。
  • 操作效率高:通过哈希函数,字典可以在平均情况下提供O(1)时间复杂度的查找、插入和删除操作。

然而,字典也有一定的开销。每个字典元素都需要额外的空间来存储哈希值、指针等元数据。此外,当哈希表发生哈希冲突时,可能需要通过链表或其他方式解决冲突,这可能会降低操作的效率。

操作优化和转换

Redis的集合实现提供了一组API来进行集合的创建、修改、查找等操作。这些API在内部会根据集合的大小和元素的特性选择合适的底层数据结构,并且在必要时进行数据结构之间的转换。

例如,当向一个使用整数集合实现的集合中添加一个新的整数元素时,如果添加后的集合仍然满足整数集合的使用条件(即元素数量没有超过预设的阈值),那么Redis会直接在整数集合的末尾添加新的元素。否则,Redis会将整数集合转换为字典,并在字典中插入新的元素。

Redis的集合在底层使用了整数集合和字典两种数据结构来实现。整数集合适用于元素较少且都是整数的场景,而字典适用于元素数量较多或元素类型不限的场景。通过这种灵活的设计,Redis能够在不同的使用场景下提供高效的操作性能,同时保持内存的低消耗和操作的快速性。

5. 有序集合的底层实现:Redis中的数据结构

Redis的有序集合(Sorted Sets)是一个有序的、元素不重复的集合,其中每个元素都关联了一个分数(score)。为了实现这种数据结构及其相关操作的高效性,Redis在底层主要使用了两种数据结构:压缩列表(ziplist)和跳表(skiplist)。

跳表(skiplist)

当有序集合的元素数量较多或元素的大小较大时,Redis会使用跳表作为底层实现。跳表是一种多层的有序链表,它通过维护多个层次的指针来加快查找、插入和删除操作的速度。
在这里插入图片描述

跳表的优势在于:

  • 查找效率高:通过维护多个层次的指针,跳表可以在平均情况下提供O(log N)时间复杂度的查找操作,其中N是元素的数量。
  • 插入和删除操作快速:跳表的插入和删除操作只需要局部地调整指针,而不需要移动大量的数据。
  • 支持范围查询:跳表可以方便地支持按照分数范围查询元素的操作

然而,跳表也有一定的开销。每个元素在跳表中都有多个指向前驱和后继的指针,这些指针会占用额外的内存空间。

在这里插入图片描述

操作优化和转换

Redis的有序集合实现提供了一组API来进行集合的创建、修改、查找等操作。这些API在内部会根据集合的大小和元素的特性选择合适的底层数据结构,并且在必要时进行数据结构之间的转换。

例如,当向一个使用压缩列表实现的有序集合中添加一个新的元素时,如果添加后的集合仍然满足压缩列表的使用条件(即元素数量没有超过预设的阈值),那么Redis会直接在压缩列表的末尾添加新的元素。否则,Redis会将压缩列表转换为跳表,并在跳表中插入新的元素。

Redis的有序集合在底层使用了压缩列表和跳表两种数据结构来实现。压缩列表适用于元素较少且大小较小的场景,而跳表适用于元素数量较多或元素大小较大的场景。通过这种灵活的设计,Redis能够在不同的使用场景下提供高效的操作性能,同时保持内存的低消耗和操作的快速性。有序集合的实现使得Redis能够支持按照分数排序、范围查询等复杂操作,满足了业务上的多样化需求。

三、总结

Redis通过精巧的数据结构和编码方式,实现了高性能的数据存储和操作。其底层实现不仅考虑了内存的使用效率,还充分考虑了数据操作的性能。这使得Redis能够在处理大量数据和并发请求时,依然保持出色的性能表现。对于开发者而言,理解Redis的数据结构和底层实现,有助于更好地使用和优化Redis,从而提升应用的整体性能。

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

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

相关文章

【云原生】Docker的镜像创建

目录 1.基于现有镜像创建 (1)首先启动一个镜像,在容器里做修改 ​编辑(2)然后将修改后的容器提交为新的镜像,需要使用该容器的 ID 号创建新镜像 实验 2.基于本地模板创建 3&am…

【网站项目】基于SSM的249作业提交与查收系统

🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板&#xff…

【Python爬虫入门到精通】小白也能看懂的知识要点与学习路线

文章目录 1. 写在前面2. 爬虫行业情况3. 学习路线 【作者主页】:吴秋霖 【作者介绍】:Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作! 【作者推荐】:对JS逆向感兴趣的朋友可以关…

计数指针:shared_ptr (共享指针)与函数 笔记

推荐B站视频: 4.shared_ptr计数指针_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV18B4y187uL?p4&vd_sourcea934d7fc6f47698a29dac90a922ba5a3 5.shared_ptr与函数_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV18B4y187uL?p5&vd_sourcea…

AI引爆算力需求,思腾推出支持大规模深度学习训练的高性能AI服务器

近日人工智能研究公司OpenAI公布了其大型语言模型的最新版本——GPT-4,可10秒钟做出一个网站,60秒做出一个游戏,参加了多种基准考试测试,它的得分高于88%的应试者;随后百度CEO李彦宏宣布正式推出大语言模型“文心一言”…

扫雷游戏——数组和函数实现

扫雷游戏的功能说明 使⽤控制台实现经典的扫雷游戏 游戏可以通过菜单实现继续玩或者退出游戏扫雷的棋盘是9*9的格⼦ 默认随机布置10个雷可以排查雷如果位置不是雷,就显⽰周围有⼏个雷如果位置是雷,就炸死游戏结束把除10个雷之外的所有⾮雷都找出来&…

域名缩短平台搭建

前言 当自己搭建的项目和网站相关文章的链接过长,可以参考一下本文搭建的平台 遵纪守法,不要乱缩网址。 代码: https://github.com/dyanst/shorturlhttps://github.com/dyanst/shorturl shorturl-main.zip官方版下载丨最新版下载丨绿色版…

Linux(linux版本 centos 7) 下安装 oracle 19c详细教程(新手小白易上手)

一、安装前准备 1、下载预安装包 wget http://yum.oracle.com/repo/OracleLinux/OL7/latest/x86_64/getPackage/oracle-database-preinstall-19c-1.0-1.el7.x86_64.rpm预安装包下载成功 2、下载oracle安装包 下载地址如下 https://www.oracle.com/cn/database/technologies…

Maven命令运行单元测试

使用idea开发多模块项目时,有时别的模块编译不通过会导致不能运行单元测试,这是我们可以使用maven命令来运行单元测试 格式 mvn -DtestDingTalkTest#getAllUsers 命令说明 mvn -Dtest 固定格式 DingTalkTest 单元测试类名 getAllUsers 单元测试方法 单元测试类和单元测试方法…

【LUA】mac状态栏添加天气

基于网络上的版本修改的,找不到出处了。第一个摸索的lua脚本,调了很久。 主要修改:如果风速不大,就默认不显示,以及调整为了一些格式 local urlApi http://.. --这个urlApi去申请个免费的就可以了 然后打开对应的json…

云轴科技ZStack成为交通运输业上云用云推进中心首批成员单位

近日,中国信息通信研究院、中国交通运输协会信息专业委员会联合发起成立“交通运输业上云用云推进中心”,上海云轴信息科技有限公司(简称云轴科技ZStack)凭借优秀的产品技术创新能力和在交通运输领域的实践经验成为首批成员单位并…

《安富莱嵌入式周报》第331期:单片机实现全功能软件无线电,开源电源EEZ升级主控,ARM 汇编用户指南,UDS统一诊断服务解析,半导体可靠性设计手册

周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 目录: 1、单片机实现低配版全功能软件无线电,范围0.5-30 MHz,支持SSB、AM、FM和CW …

浅谈电气火灾监控系统应用在某地铁车站

安科瑞电气股份有限公司 上海嘉定201801 摘要:根据国家有关规范对建筑电气火灾监测系统设置的要求,结合当地城市地铁供配电方案的特点,介绍了地铁站电气火灾监测系统设置方案,从电气火灾探测器的选择和位置设置、电气火灾监测设备…

Java项目:SSM框架基于spring+springmvc+mybatis实现的心理预约咨询管理系统(ssm+B/S架构+源码+数据库+毕业论文)

一、项目简介 本项目是一套ssm823基于SSM框架的心理预约咨询管理系统,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含:项目源码、数据库脚本等,该项目附带全部源码可作为毕设使用。 项目都经过严格调试&am…

WWDG喂狗

3F 是0111111 40 是1000000 0X7F 127 0X5F 95 127-9532 注意:中断是在0x40,在0x40喂狗则程序不会复位 在0x5F之前喂狗会复位,减小到63以下也会复位 在0x5F与0x3F之间喂狗会继续执行,不会复位 WWDG_HandleTypeDef WWDG_Handler; //窗口看门狗句柄//初始化窗口看门狗…

Java接收curl发出的中文请求无法解析

最近做项目遇到了这种情况,Java接收curl发出的中文请求无法解析,英文请求一切正常,中文请求则对方服务器无法解析,可以猜测是中文导致的编码问题,但是奇怪的是,本地输出json也没有乱码,编解码正…

洛谷刷题-【入门2】分支结构

目录 1.苹果和虫子 题目描述 输入格式 输出格式 输入输出样例 2.数的性质 题目描述 输入格式 输出格式 输入输出样例 3.闰年判断 题目描述 输入格式 输出格式 输入输出样例 4.apples 题目描述 输入格式 输出格式 输入输出样例 5.洛谷团队系统 题目描述 …

【数学笔记】一元n次不等式,分式不等式,绝对值不等式

不等式 基本性质 一元n次不等式一元二次不等式一元高次不等式分式不等式绝对值不等式 基本性质 性质 a > b ⇔ b < a a>b\Leftrightarrow b<a a>b⇔b<a a > b , b > c ⇒ a > c a>b,b>c\Rightarrow a>c a>b,b>c⇒a>c a > b ,…

Hbuilder从gitlab上面拉取项目

要先下载TortoiseGit-2.15.0.0-64bit这个软件 在HBuilder中从GitLab上拉取项目&#xff0c;请按照以下步骤操作&#xff1a; 1. 打开HBuilder&#xff0c;点击左上角的“文件”菜单&#xff0c;然后选择“新建”->“项目”。 2. 在弹出的对话框中&#xff0c;选择“从Git导…

redis主从复制薪火相传

一.主从复制 1、是什么 主机数据更新后根据配置和策略&#xff0c; 自动同步到备机的master/slaver机制&#xff0c;Master以写为主&#xff0c;Slave以读为主 2、能干嘛 读写分离&#xff0c;性能扩展&#xff08;主 写 从 读&#xff09; 容…