写点什么

【Redis 技术进阶之路】「原理分析系列开篇」探索事件驱动枚型与数据特久化原理实现(数据持久化的实现 RDB)

作者:码界西柚
  • 2025-03-20
    江苏
  • 本文字数:5428 字

    阅读完需:约 18 分钟

【Redis技术进阶之路】「原理分析系列开篇」探索事件驱动枚型与数据特久化原理实现(数据持久化的实现RDB)

【专栏简介】

随着数据需求的迅猛增长,持久化和数据查询技术的重要性日益凸显。关系型数据库已不再是唯一选择,数据的处理方式正变得日益多样化。在众多新兴的解决方案与工具中,Redis 凭借其独特的优势脱颖而出。

【技术大纲】

为何 Redis 备受瞩目?原因在于其学习曲线平缓,短时间内便能对 Redis 有初步了解。同时,Redis 在处理特定问题时展现出卓越的通用性,专注于其擅长的领域。深入了解 Redis 后,您将能够明确哪些任务适合由 Redis 承担,哪些则不适宜。这一经验对开发人员来说是一笔宝贵的财富。



在这个专栏中,我们将专注于 Redis 的 6.2 版本进行深入分析和介绍。Redis 6.2 不仅是我个人特别偏爱的一个版本,而且在实际应用中也被广泛认为是稳定性和性能表现都相当出色的版本

【专栏目标】

本专栏深入浅出地传授 Redis 的基础知识,旨在助力读者掌握其核心概念与技能。深入剖析了 Redis 的大多数功能以及全部多机功能的实现原理,详细展示了这些功能的核心数据结构和关键算法思想。读者将能够快速且有效地理解 Redis 的内部构造和运作机制,这些知识将助力读者更好地运用 Redis,提升其使用效率。


将聚焦于 Redis 的五大数据结构,深入剖析各种数据建模方法,并分享关键的管理细节与调试技巧。

【目标人群】

Redis 技术进阶之路专栏:目标人群与受众对象,对于希望深入了解 Redis 实现原理底层细节的人群

1. Redis 爱好者与社区成员

Redis 技术有浓厚兴趣,经常参与社区讨论,希望深入研究 Redis 内部机制、性能优化和扩展性的读者。

2. 后端开发和系统架构师

在日常工作中经常使用 Redis 作为数据存储和缓存工具,他们在项目中需要利用 Redis 进行数据存储、缓存、消息队列等操作时,此专栏将为他们提供有力的技术支撑。

3. 计算机专业的本科生及研究生

对于学习计算机科学、软件工程、数据分析等相关专业的在校学生,以及对 Redis 技术感兴趣的教育工作者,此专栏可以作为他们的学习资料和教学参考。


无论是初学者还是资深专家,无论是从业者还是学生,只要对 Redis 技术感兴趣并希望深入了解其原理和实践,都是此专栏的目标人群和受众对象


让我们携手踏上学习 Redis 的旅程,探索其无尽的可能性!



Redis 数据持久化的必要

鉴于 Redis 是一种基于内存的数据库系统,其核心优势在于将数据库状态直接存储在高速的内存空间之中。然而,这种设计也带来了一项重要考量:若未采取适当的持久化措施,将内存中的数据库状态安全地转移到磁盘上,那么随着服务器进程的终止,这些宝贵的数据将面临丢失的风险,因为内存中的数据不具备持久性,断电或进程退出均会导致其消失无踪。因此,为确保数据的安全与连续性,Redis 提供了多种持久化机制,以在服务器运行之外保护并恢复数据库状态,避免数据的意外丢失。

Redis 数据持久化的实现

Redis 作为一种高效的键值对存储系统,其核心功能在于提供一个灵活且强大的数据库服务器环境。此服务器架构内,通常承载着若干个非空的数据库实例,每个实例均具备独立的空间以容纳任意数量的键值对数据。为了简化理解与操作,我们习惯上将 Redis 服务器中所有非空数据库及其内部存储的键值对集合,统一抽象并称之为“数据库状态”。



为了更直观地说明,请考虑上面的图所呈现的示例,它生动地展示了一个 Redis 服务器的内部构造,其中包含了三个非空的数据库实例。这三个数据库,连同它们各自存储的丰富键值对集合,共同构成了该 Redis 服务器在某一时刻的完整数据库状态。


为了有效应对内存数据易失性的问题,Redis 巧妙地引入了 RDB(Redis Database)持久化功能。这一创新机制能够定时或按需将 Redis 内存中的完整数据库状态快照保存至磁盘之上,从而构建起一道坚实的防线,防止数据因系统崩溃、意外断电等突发状况而遭受不可挽回的损失。

RDB 的持久化机制

RDB 持久化机制在 Redis 中展现了高度的灵活性与可配置性,它既可以响应管理员的手动触发,根据即时需求执行数据快照;也可以根据服务器的预设配置,实现定时自动备份,确保数据更新的连续记录。这一过程将选定时间点的数据库状态精准捕捉,并封装成一个紧凑的、经过优化的二进制 RDB 文件(如下图所示),有效减少了存储空间占用,同时保证了数据的完整性与一致性。



尤为重要的是,这个 RDB 文件不仅是数据的简单集合,更是 Redis 数据库状态在某一历史时刻的忠实记录。在需要时,通过加载这一文件,可以迅速且准确地还原至生成 RDB 文件时的数据库状态(如下图所示),为数据的恢复与迁移提供了强有力的支持。



由于 RDB 文件被妥善保存在硬盘上,这一特性确保了即便 Redis 服务器进程意外终止,乃至承载 Redis 服务器的计算机遭遇停机情况,只要 RDB 文件保持完好无损,Redis 服务器便能够凭借该文件恢复数据库的先前状态,从而保障数据的持久性和可恢复性。

RDB 文件的创建与载入

Redis 提供了两个关键的命令来生成 RDB 文件,以实现数据的持久化。其中,SAVE命令是一个同步操作,它会立即阻塞 Redis 服务器,直到 RDB 文件被完整创建并保存到硬盘上为止。而BGSAVE命令则采用异步方式执行,它会在后台启动一个子进程来负责生成 RDB 文件,从而避免了阻塞主服务器进程,确保了 Redis 服务的连续性和响应性。这两个命令共同为 Redis 用户提供了灵活的数据备份和恢复选项。

SAVE

当执行SAVE命令时,Redis 服务器进程会进入阻塞状态,这意味着在 RDB 文件被完整创建并安全保存到硬盘之前,服务器将暂停处理任何来自客户端的命令请求。此期间,所有试图与 Redis 服务器交互的操作都将被挂起,直至SAVE命令完成,服务器恢复正常运行状态。


//等待直到RDB文件创建完半redis>SAVEOK
复制代码


因此,虽然SAVE命令能够确保数据的即时持久化,但在高并发或需要低延迟响应的场景中,其阻塞特性可能需要谨慎使用。

BGSAVE

和 SAVE 命令直接阻塞服务器进程的做法不同,BGSAVE 命令会派生出一个子进程,然后由子进程负责创建 RDB 文件,服务器进程(父进程)继续处理命令请求。


//派生子进程,并由子进程创建RDB文件redis>BGSAVEBackground saving started
复制代码


RDB 文件的创建过程实际上是由 rdb.c 源文件中的 rdbSave 函数精心实现的。SAVE 命令与 BGSAVE 命令虽共享这一核心功能,却以截然不同的方式调用 rdbSave 函数,它们之间的差异通过以下简化的伪代码清晰展现:


function SAVE():      # 直接在主线程中调用 rdbSave 函数,这会阻塞服务器直到文件生成完成      rdbSave()      # 阻塞期间,服务器无法处理任何命令请求    function BGSAVE():      # 在后台启动一个新进程来执行 rdbSave 函数      # 这样做不会阻塞主服务器进程,允许其继续处理命令请求      forkChildProcess()      if (inChildProcess()):          rdbSave()          exitChildProcess()  
复制代码


主服务器进程在执行BGSAVE命令时,能够继续其正常的操作流程,完全不受rdbSave操作的影响,这一特性通过创建独立的子进程来实现异步处理。

SAVE 与 BGSAVE 命令在逻辑层面的主要差异
  • 处理的阻塞模式以及线程执行的区别SAVE命令采取的是直接在主线程中同步执行的方式,这不可避免地会导致服务器在处理 RDB 文件生成期间无法响应其他命令请求,从而造成阻塞;而BGSAVE命令则巧妙地绕过了这一问题,它通过创建一个新的子进程来异步执行rdbSave操作,这样主服务器进程就能够持续运行,不受任何干扰,确保了 Redis 服务的高可用性和低延迟响应。

  • 使用 SAVE 命令或者 BGSAVE 命令创建 RDB 文件不同:与创建 RDB 文件的操作(通过SAVEBGSAVE命令)不同,RDB 文件的加载过程是自动且无需用户干预的。当 Redis 服务器启动时,它会检查是否存在 RDB 文件。一旦检测到 RDB 文件的存在,Redis 服务器将自动启动加载流程,无需任何专门的加载命令。

Redis 服务器在启动时打印

Redis 服务器在启动时生成的一系列日志记录中的一个关键条目。特别地,当日志中出现“DB loaded from disk: ...”这一条目时,它标志着 Redis 服务器已经成功地自动从磁盘上加载了 RDB 文件。


[1279]17 Aug 0:07:01.270 Server started,Redis version 2.9.11[1279]17 Aug 0:07:01.289 DB loaded from disk:0.018 seconds[1279]17 Aug 0:07:01.289 The server is now ready to accept connections on port6379
复制代码


这一操作通常在服务器启动过程的早期阶段进行,确保了 Redis 数据库能够迅速恢复到最近一次持久化保存的状态,为用户和应用程序提供了无缝的数据访问体验。



具体到 RDB 文件的加载过程,rdb.c/rdbLoad函数扮演着核心角色,它负责从 RDB 文件中读取并恢复数据库的所有数据。这个函数与用于创建 RDB 文件的rdbSave函数在功能上相互对应,它们之间的关系可以通过上图直观展现,共同构成了 Redis 数据持久化与恢复机制的重要组成部分。

设置保存条件

在 Redis 服务器启动之际,用户拥有高度的灵活性来配置其行为,特别是通过明确指定配置文件或直接在启动命令中传入参数的方式来设置save选项。这一机制赋予了用户精确控制数据持久化策略的能力。


save 900 1save 300 10save 60 10000
复制代码


若用户选择不显式设置save选项,Redis 服务器则会采取一种预设的、保守的策略来自动配置这些条件,确保数据在特定条件下能够自动保存到磁盘上,以此作为默认的数据持久化保障措施。


struct redisserver{  //记录了保存条件的数组  struct saveparam saveparams;};
复制代码
saveparams

saveparams属性被精心设计为一个数组结构,其中每一个元素都承载着saveparam结构体的精髓。这些saveparam结构体各自独立,扮演着至关重要的角色,它们分别存储了关于save选项配置的具体保存条件。


struct saveparam{  /1秒数  timet seconds;  //修改数  int changes;};
复制代码


在 Redis 服务器的状态结构中,saveparams数组将以一种直观且结构化的方式呈现,其布局大致对应于下图所示。该图不仅清晰地展示了saveparams数组的组织形式,还详细描绘了数组中每个saveparam元素的具体内容,包括它们各自代表的数据保存条件。


检查保存条件是否满足

Redis 的服务器内部设有一个周期性的操作函数serverCron,它默认以每 100 毫秒为周期自动执行,肩负起维护服务器稳定运行的重任。在serverCron的众多职责之中,尤为关键的一项便是定期审查save选项所预设的数据保存条件。一旦这些条件得以满足,serverCron便会立即触发BGSAVE命令,以一种非阻塞的方式启动数据的后台保存流程。


以下伪代码展示了 serverCron 函数检查保存条件的过程,服务器周期性操作函数,默认每隔一定时间执行一次,用于维护服务器状态


def servercron():      # 遍历所有保存条件      for saveparam in server.saveparams:          # 计算距离上次执行保存操作的时间(秒)          elapsed_time = time.time() - server.lastsave                    # 如果数据库状态的修改次数超过条件所设置的次数          # 并且距离上次保存的时间超过条件所设置的时间          if server.dirty > saveparam.changes and elapsed_time >= saveparam.seconds:              # 执行BGSAVE命令,以非阻塞方式保存数据库              BGSAVE()              # (可选)更新lastsave时间和其他可能的维护操作              server.lastsave = time.time()              # 这里可以添加更多维护操作的代码  
复制代码


程序会全面遍历saveparams数组,细致地检查数组中的每一个保存条件。一旦发现有任意一个条件得到了满足,程序便会迅速响应,指示服务器执行BGSAVE命令。

AOF 的持久化机制

当 Redis 服务器启用了 AOF 持久化功能时,为了确保数据的完整性和最新性,服务器会优先选择 AOF 文件来恢复数据库状态。反之,仅当 AOF 持久化功能被禁用或 AOF 文件不可用(例如,文件损坏)时,服务器才会回退到使用 RDB 文件来进行数据库状态的还原。


服务器决定采用何种文件来恢复数据库状态的这一逻辑流程,如下图所示,它通过智能判断,确保了在各种情况下都能以最合适的方式恢复数据。


最终总结

SAVE 命令执行时的服务器状态

SAVE命令被触发执行时,Redis 服务器会进入一种阻塞状态,这意味着在此期间,服务器将暂停处理任何来自客户端的新命令请求。因此,若SAVE命令正处于执行过程中,所有尝试与服务器通信的客户端所发送的命令请求都将遭遇拒绝,服务器不会对这些请求进行任何处理或响应。


直至SAVE命令圆满完成其数据持久化任务,并重新开放对外部命令的接收窗口后,Redis 服务器才会开始逐一处理并响应那些之前因阻塞而积压的客户端命令请求。这一过程确保了数据的一致性和完整性,尽管在某些情况下可能会暂时影响服务的响应性。

BGSAVE 命令执行时的服务器状态

由于BGSAVE命令巧妙地利用了子进程来执行数据的保存工作,Redis 服务器在子进程忙于创建 RDB 文件的同时,能够维持其正常的运行状态,继续高效地处理来自客户端的命令请求。然而,值得注意的是,在BGSAVE命令的执行周期内,服务器对于SAVEBGSAVE以及BGREWRITEAOF这三个特定命令的处理逻辑会经历一些调整,以确保数据的一致性和操作的合理性。


具体而言,当BGSAVE正在运行时,如果客户端尝试执行SAVE命令(该命令会阻塞服务器直到完成全量快照),Redis 服务器可能会拒绝执行该命令,或者返回一个指示当前已有后台保存任务在运行的响应,以避免不必要的资源竞争和潜在的性能瓶颈。对于BGSAVE命令本身的重复请求,服务器可能会选择忽略或返回一个提示信息,指出一个后台保存任务已经在进行中,无需重复启动。

RDB 文件载入时的服务器状态

在服务器载入 RDB 文件的过程中,它会全神贯注地投入到这一关键任务中,期间将维持阻塞状态,以确保载入工作的顺利进行和数据的完整性。这种阻塞状态将持续到 RDB 文件被完全载入并恢复为内存中的数据结构为止,之后服务器才会重新恢复对外部请求的响应,继续提供其丰富的服务功能。

发布于: 刚刚阅读数: 3
用户头像

码界西柚

关注

🏆 InfoQ写作平台-签约作者 🏆 2020-03-25 加入

👑 优酷资深工程师 | INTJ | 狮子座 | 高洞察力理性自律小i人 📕 个人著作《深入浅出Java虚拟机—JVM原理与实战》 💻 10年开发经验,参与过多个大型互联网项目,定期分享技术干货和项目经验

评论

发布
暂无评论
【Redis技术进阶之路】「原理分析系列开篇」探索事件驱动枚型与数据特久化原理实现(数据持久化的实现RDB)_redis_码界西柚_InfoQ写作社区