Redis 数据存储原理

这篇具有很好参考价值的文章主要介绍了Redis 数据存储原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

核心模块如图 1-10。

Redis 数据存储原理

图1-10

图 1-10

  • Client 客户端,官方提供了 C 语言开发的客户端,可以发送命令,性能分析和测试等。

  • 网络层事件驱动模型,基于 I/O 多路复用,封装了一个短小精悍的高性能 ae 库,全称是 a simple event-driven programming library

    • 在 ae 这个库里面,我通过 aeApiState 结构体对 epoll、select、kqueue、evport四种 I/O 多路复用的实现进行适配,让上层调用方感知不到在不同操作系统实现 I/O 多路复用的差异。

    • Redis 中的事件可以分两大类:一类是网络连接、读、写事件;另一类是时间事件,也就是特定时间触发的事件,比如定时执行 rehash 操作。

  • 命令解析和执行层,负责执行客户端的各种命令,比如 SET、DEL、GET等。

  • 内存分配和回收,为数据分配内存,提供不同的数据结构保存数据。

  • 持久化层,提供了 RDB 内存快照文件 和 AOF 两种持久化策略,实现数据可靠性。

  • 高可用模块,提供了副本、哨兵、集群实现高可用。

  • 监控与统计,提供了一些监控工具和性能分析工具,比如监控内存使用、基准测试、内存碎片、bigkey 统计、慢指令查询等。

掌握了整体架构和模块后,接下来进入 src 源码目录,使用如下指令执行 redis-server可执行程序启动 Redis。

./redis-server ../redis.conf

每个被启动的服务我都会抽象成一个 redisServer,源码定在server.h 的redisServer 结构体。

这个结构体包含了存储键值对的数据库实例、redis.conf 文件路径、命令列表、加载的 Modules、网络监听、客户端列表、RDB AOF 加载信息、配置信息、RDB 持久化、主从复制、客户端缓存、数据结构压缩、pub/sub、Cluster、哨兵等一些列 Redis 实例运行的必要信息。

结构体字段很多,不再一一列举,部分核心字段如下。

truct redisServer {
    pid_t pid;  /* 主进程 pid. */
    pthread_t main_thread_id; /* 主线程 id */
    char *configfile;  /*redis.conf 文件绝对路径*/
    redisDb *db; /* 存储键值对数据的 redisDb 实例 */
   int dbnum;  /* DB 个数 */
    dict *commands; /* 当前实例能处理的命令表,key 是命令名,value 是执行命令的入口 */
    aeEventLoop *el;/* 事件循环处理 */
    int sentinel_mode;  /* true 则表示作为哨兵实例启动 */

   /* 网络相关 */
    int port;/* TCP 监听端口 */
    list *clients; /* 连接当前实例的客户端列表 */
    list *clients_to_close; /* 待关闭的客户端列表 */

    client *current_client; /* 当前执行命令的客户端*/
};

1.2.1 数据存储原理

其中redisDb *db指针非常重要,它指向了一个长度为 dbnum(默认 16)的 redisDb 数组,它是整个存储的核心,我就是用这玩意来存储键值对。

redisDb

redisDb 结构体定义如下。

typedef struct redisDb {
    dict *dict; 
    dict *expires; 
    dict *blocking_keys;
    dict *ready_keys;
    dict *watched_keys; 
    int id;          
    long long avg_ttl; 
    unsigned long expires_cursor;
    list *defrag_later; 
    clusterSlotToKeyMapping *slots_to_keys;
} redisDb;

dict 和 expires

  • dict 和 expires 是最重要的两个属性,底层数据结构是字典,分别用于存储键值对数据和 key 的过期时间。

  • expires,底层数据结构是 dict 字典,存储每个 key 的过期时间。

MySQL:“为什么分开存储?”

好问题,之所以分开存储,是因为过期时间并不是每个 key 都会设置,它不是键值对的固有属性,分开后虽然需要两次查找,但是能节省内存开销。

blocking_keys 和 ready_keys

底层数据结构是 dict 字典,主要是用于实现 BLPOP 等阻塞命令。

当客户端使用 BLPOP 命令阻塞等待取出列表元素的时候,我会把 key 写到 blocking_keys 中,value 是被阻塞的客户端。

当下一次收到 PUSH 命令执时,我会先检查 blocking_keys 中是否存在阻塞等待的 key,如果存在就把 key 放到 ready_keys 中,在下一次 Redis 事件处理过程中,会遍历 ready_keys 数据,并从 blocking_keys 中取出阻塞的客户端响应。

watched_keys

用于实现 watch 命令,存储 watch 命令的 key。

id

Redis 数据库的唯一 ID,一个 Redis 服务支持多个数据库,默认 16 个。

avg_ttl

用于统计平均过期时间。

expires_cursor

统计过期事件循环执行的次数。

defrag_later

保存逐一进行碎片整理的 key 列表。

slots_to_keys

仅用于 Cluster 模式,当使用 Cluster 模式的时候,只能有一个数据库 db 0。slots_to_keys 用于记录 cluster 模式下,存储 key 与哈希槽映射关系的数组。

dict

Redis 使用 dict 结构来保存所有的键值对(key-value)数据,这是一个散列表,所以 key 查询时间复杂度是 O(1) 。

所谓散列表,我们可以类比 Java 中的 HashMap,其实就是一个数组,数组的每个元素叫做哈希桶。

dict 结构体源码在 dict.h 中定义。

struct dict {
    dictType *type;

    dictEntry **ht_table[2];
    unsigned long ht_used[2];

    long rehashidx;

    int16_t pauserehash;
    signed char ht_size_exp[2];
};

dict 的结构体里,有 dictType *type**ht_table[2]long rehashidx 三个很重要的结构。

  • type 存储了 hash 函数,key 和 value 的复制等函数;

  • ht_table[2],长度为 2 的数组,默认使用 ht_table[0] 存储键值对数据。我会使用 ht_table[1] 来配合实现渐进式 reahsh 操作。

  • rehashidx 是一个整数值,用于标记是否正在执行 rehash 操作,-1 表示没有进行 rehash。如果正在执行 rehash,那么其值表示当前 rehash 操作执行的 ht_table[1] 中的 dictEntry 数组的索引。

  • pauserehash 表示 rehash 的状态,大于 0 时表示 rehash 暂停了,小于 0 表示出错了。

  • ht_used[2],长度为 2 的数组,表示每个哈希桶存储了多少个 键值对实体(dictEntry),值越大,哈希冲突的概率越高。

  • ht_size_exp[2],每个散列表的大小,也就是哈希桶个数。

重点关注 ht_table 数组,数组每个位置叫做哈希桶,就是这玩意保存了所有键值对,每个哈希桶的类型是 dictEntry。

MySQL:“Redis 支持那么多的数据类型,哈希桶咋保存?”

他的玄机就在 dictEntry 中,每个 dict 有两个 ht_table,用于存储键值对数据和实现渐进式 rehash。

dictEntry 结构如下。

typedef struct dictEntry {
    void *key;
    union {
       // 指向实际 value 的指针
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    // 散列表冲突生成的链表
    struct dictEntry *next;
    void *metadata[];
} dictEntry;
  • *key 指向键值对的键的指针,指向一个 sds 对象,key 都是 string 类型。

  • v 是键值对的 value 值,是个 union(联合体),当它的值是 uint64_t、int64_t 或 double 数字类型时,就不再需要额外的存储,这有利于减少内存碎片。(为了节省内存操碎了心)当值为非数字类型,就是用 val 指针存储。

  • *next指向另一个 dictEntry 结构, 多个 dictEntry 可以通过 next 指针串连成链表, 从这里可以看出, ht_table 使用链地址法来处理键碰撞:当多个不同的键拥有相同的哈希值时,哈希表用一个链表将这些键连接起来。

哈希桶并没有保存值本身,而是指向具体值的指针,从而实现了哈希桶能存不同数据类型的需求

redisObject

dictEntry 的 *val 指针指向的值实际上是一个 redisObject 结构体,这是一个非常重要的结构体。

我的 key 只能是字符串类型,而 value 可以是 String、Lists、Set、Sorted Set、散列表等数据类型。

键值对的值都被包装成 redisObject 对象, redisObject 在 server.h 中定义。

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS;
    int refcount;
    void *ptr;
} robj;
  • type:记录了对象的类型,string、set、hash 、Lis、Sorted Set 等,根据该类型来确定是哪种数据类型,使用什么样的 API 操作。

  • encoding:编码方式,表示 ptr 指向的数据类型具体数据结构,即这个对象使用了什么数据结构作为底层实现保存数据。同一个对象使用不同编码实现内存占用存在明显差异,内部编码对内存优化非常重要。

  • lru:LRU_BITS:LRU 策略下对象最后一次被访问的时间,如果是 LFU 策略,那么低 8 位表示访问频率,高 16 位表示访问时间。

  • refcount :表示引用计数,由于 C 语言并不具备内存回收功能,所以 Redis 在自己的对象系统中添加了这个属性,当一个对象的引用计数为 0 时,则表示该对象已经不被任何对象引用,则可以进行垃圾回收了。

  • ptr 指针:指向对象的底层实现数据结构,指向值的指针

如图 1-11 是由 redisDb、dict、dictEntry、redisObejct 关系图:

Redis 数据存储原理

图1-11

图 1-11

注意,一开始的时候,我只使用 ht_table[0] 这个散列表读写数据,ht_table[1] 指向 NULL,当这个散列表容量不足,触发扩容操作,这时候就会创建一个更大的散列表 ht_table[1]。

接着我会使用渐进式 rehash 的方式将 ht_table[0] 的数据迁移到 ht_table[1] 上,全部迁移完成后,再修改下指针,让 ht_table[0] 指向扩容后的散列表,回收掉原来的散列表,ht_table[1] 再次指向 NULL。文章来源地址https://www.toymoban.com/news/detail-424293.html

到了这里,关于Redis 数据存储原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • redis 存储结构原理 1

    关于 redis 相信大家都不陌生了,之前有从 0 -1 分享过 redis 的基本使用方式,用起来倒是都没有啥问题了,不过还是那句话, 会应用之后,我们必须要究其原理,知其然知其所以然 今天我们来分享一下关于 redis 的存储结构的原理 我们都知道 redis 是一个 K-V 内存数据库,类似

    2024年02月12日
    浏览(32)
  • redis 存储结构原理 2

    咱们接着上一部分来进行分享,我们可以在如下地址下载 redis 的源码 : https://redis.io/download 此处我下载的是 redis-6.2.5 版本的,xdm 可以直接下载上图中的 **redis-6.2.6 **版本, redis hash 表的数据结构定义在: redis-6.2.5srcdict.h 哈希表的结构,每一个字典都有两个实现从旧表到新

    2024年02月12日
    浏览(36)
  • 3、深入解析Redis Cluster集群运维与核心原理

    在今天的大规模分布式系统中,Redis Cluster已经成为了许多企业选择的分布式缓存方案之一。了解Redis Cluster的运维及核心原理对于确保系统的高可用性和性能至关重要。本文将深入探讨Redis Cluster集群的运维细节和核心原理,以帮助读者更好地理解和优化Redis在集群环境下的表

    2024年01月16日
    浏览(42)
  • 【大虾送书第七期】深入浅出SSD:固态存储核心技术、原理与实战

    目录  ✨写在前面   ✨内容简介  ✨作者简介  ✨名人推荐  ✨文末福利      🦐博客主页:大虾好吃吗的博客      🦐专栏地址:免费送书活动专栏地址         近年来国家大力支持半导体行业,鼓励自主创新,中国SSD技术和产业良性发展,产业链在不断完善,与

    2024年02月10日
    浏览(54)
  • 【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(集群指令分析—上篇)

    Redis Cluster提供了一套完整的功能技术,使得Redis能够以分布式的方式运行,并具备高可用性、容错性和扩展性。通过自动发现、主从选举、在线分片等机制,Redis Cluster能够自动管理集群中的节点,并保证数据的一致性和可靠性。同时,基于配置文件和转向机制,Redis Cluster能

    2024年02月14日
    浏览(52)
  • 《计算机组成原理》期末考试手写笔记——模块五: 并行主存系统(交叉存储器+顺序存储器“带宽”的计算方法)

    目录 (一)知识点总结   (二)经典考试例题 1.设主存储器容量为256字,字长为32位,模块数m=4,分别用顺序方式和交叉方式进行组织。主存储器的存储周期T=200ns,数据总线宽度为32位,总线传送周期τ=50ns。若按地址顺序连续读取4个字,问顺序存储器和交叉存储器的带宽各

    2024年02月08日
    浏览(49)
  • redis核心数据结构

    redis下载地址:Download | Redis linux进入redis目录首先使用make命令进行c++的编译,修改redis.conf文件: 启动退出相关命令: redis五种数据结构图: 1、常用命令 2、应用场景 1)、单值缓存 2)、对象缓存 3)、分布式锁  4)、计数器 5) 、计数器 6) 、分布式系统全局序列号 1、

    2024年02月09日
    浏览(39)
  • Redis核心数据结构之压缩列表(一)

    压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。 例子 例如,执行以下命令将创建一个压缩列表实现的列表键,列表键

    2024年03月16日
    浏览(37)
  • 一次可编程的非易失性存储器(OTP NVM)工作原理、eFuse模块解析

    Author: Nirvana Of Phoenixl Proverbs for you:There is no doubt that good things will always come, and when it comes late, it can be a surprise. 本文主要用于通过分析eFuse工作原理及其模式   不同于大多数FPGA使用的SRAM阵列,eFuse一次只有一根熔丝能够被编程,这是该方法的配置能力存在限制范围的原因

    2024年01月20日
    浏览(42)
  • Redis核心数据结构实战与高性能解析

    目录 一、安装Redis 二、Redis线程与高性能 2.1 Redis是单线程么? 2.2 Redis读写是单线程为何这么快? 2.3 Redis如何处理并发操作命令? 三、核心数据结构实战 3.1 字符串常用操作实战 SET 存入键值对 SETNX SETEX MSET 批量存入键值对 MSETNX DECR 原子减1 DECRBY 原子减 INCR 原子加1 INCRBY 原子

    2024年02月07日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包