面试必问的 Redis:RDB、AOF、混合持久化

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 面试必问的 Redis:RDB、AOF、混合持久化

前言

 


本来说Redis 3篇,但是上周写持久化时发现持久化的内容还越多的,于是持久化就单拆一篇了。

 

我估计后面的主从复制、哨兵、集群内容也是不少,所以说实话,我也不知道之前说的3篇会拆成几篇了。

 image.png

 

持久化机制的内容大纲其实很早就有了,但是实际写的时候断断续续写了有两周。

 

主要细节还是挺多的,在翻源码的过程中,会遇到一些疑惑点,也发现一些自己以前不知道的知识点,所以自己也要花点时间去搞清楚。

 

慢工出细活吧,本文还是有很多非常细节的内容的,如果能掌握,让大厂面试官眼前一亮还是问题不大的。

image.png 

 

 

 

正文

 


Redis 核心主流程

 

AOF RDB 的持久化过程中,有不少操作是在时间事件 serverCron 中被触发的。所以,这边有必要先了解下Redis 中的事件核心流程。

 

Redis 的服务器进程就是一个事件循环,最重要的有两个事件:文件事件和时间事件。Redis 在服务器初始化后,会无限循环,处理产生的文件事件和时间事件。

 

文件事件常见的有:接受连接(accept)、读取(read)、写入(write)、关闭连接(close)等。

 

时间事件中常见的就是 serverCronredis 核心流程中通常也只有这个时间事件。serverCron 默认配置下每100ms会被触发一次,在该时间事件中,会执行很多操作:清理过期键、AOF 后台重写、RDB save point 的检查、将 aof_buf 内容写到磁盘上(flushAppendOnlyFile 函数)等等。

 

Redis 的核心主流程如下图:

 image.png

image.png

 

相关源码在server.cae.c,核心方法是:mainaeProcessEvents

 

 

Redis 的持久化机制有哪几种


 

RDBAOF、混合持久化(redis4.0引入)

 

 

RDB的实现原理、优缺点

 


描述:类似于快照。在某个时间点,将 Redis 在内存中的数据库状态(数据库的键值对等信息)保存到磁盘里面。RDB持久化功能生成的 RDB 文件是经过压缩的二进制文件。

 

命令:有两个Redis 命令可以用于生成 RDB 文件,一个是 SAVE,另一个是BGSAVE

 

开启:使用save point 配置,满足 save point 条件后会触发 BGSAVE 来存储一次快照,这边的 save point 检查就是在上文提到的 serverCron 中进行。

 

save point 格式:save <seconds> <changes>,含义是 Redis 如果在 seconds 秒内数据发生了changes 次改变,就保存快照文件。例如 Redis 默认就配置了以下3个:

save 900 1 #900秒内有1个key发生了变化,则触发保存RDB文件save 300 10 #300秒内有10个key发生了变化,则触发保存RDB文件save 60 10000 #60秒内有10000个key发生了变化,则触发保存RDB文件

关闭:1)注释掉所有save point 配置可以关闭 RDB 持久化。2)在所有save point 配置后增加:save "",该配置可以删除所有之前配置的 save point

save ""

 

SAVE:生成 RDB 快照文件,但是会阻塞主进程,服务器将无法处理客户端发来的命令请求,所以通常不会直接使用该命令。

 

BGSAVEfork 子进程来生成RDB 快照文件,阻塞只会发生在 fork 子进程的时候,之后主进程可以正常处理请求,详细过程如下图:

 image.png

image.png


fork:在 Linux 系统中,调用fork() 时,会创建出一个新进程,称为子进程,子进程会拷贝父进程的 page table。如果进程占用的内存越大,进程的 page table 也会越大,那么fork 也会占用更多的时间。如果 Redis 占用的内存很大,那么在 fork 子进程时,则会出现明显的停顿现象。

 

RDB 的优点:


1RDB 文件是是经过压缩的二进制文件,占用空间很小,它保存了Redis 某个时间点的数据集,很适合用于做备份。 比如说,你可以在最近的 24 小时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个RDB 文件。这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。

 

2RDB 非常适用于灾难恢复(disaster recovery):它只有一个文件,并且内容都非常紧凑,可以(在加密后)将它传送到别的数据中心。

 

3RDB 可以最大化 redis 的性能。父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。

 

4RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

 

RDB 的缺点:


1RDB 在服务器故障时容易造成数据的丢失。RDB 允许我们通过修改 save point 配置来控制持久化的频率。但是,因为 RDB 文件需要保存整个数据集的状态,所以它是一个比较重的操作,如果频率太频繁,可能会对 Redis 性能产生影响。所以通常可能设置至少5分钟才保存一次快照,这时如果 Redis 出现宕机等情况,则意味着最多可能丢失5分钟数据。

 

2RDB 保存时使用 fork 子进程进行数据的持久化,如果数据比较大的话,fork 可能会非常耗时,造成 Redis 停止处理服务N毫秒。如果数据集很大且 CPU 比较繁忙的时候,停止服务的时间甚至会到一秒。

 

3Linux fork 子进程采用的是 copy-on-write 的方式。在Redis 执行 RDB 持久化期间,如果 client 写入数据很频繁,那么将增加 Redis 占用的内存,最坏情况下,内存的占用将达到原先的2倍。刚fork 时,主进程和子进程共享内存,但是随着主进程需要处理写操作,主进程需要将修改的页面拷贝一份出来,然后进行修改。极端情况下,如果所有的页面都被修改,则此时的内存占用是原先的2倍。

 

相关源码在rdb.c,核心方法是:rdbSaveBackgroundrdbSave

 

 

AOF的实现原理、优缺点

 


描述:保存Redis 服务器所执行的所有写操作命令来记录数据库状态,并在服务器启动时,通过重新执行这些命令来还原数据集。

 

开启:AOF持久化默认是关闭的,可以通过配置:appendonly yes 开启。

 

关闭:使用配置appendonly no 可以关闭 AOF 持久化。

 

AOF 持久化功能的实现可以分为三个步骤:命令追加、文件写入、文件同步。

 

命令追加:当AOF 持久化功能打开时,服务器在执行完一个写命令之后,会将被执行的写命令追加到服务器状态的 aof 缓冲区(aof_buf)的末尾。

 

文件写入与文件同步:可能有人不明白为什么将 aof_buf 的内容写到磁盘上需要两步操作,这边简单解释一下。

 

Linux 操作系统中为了提升性能,使用了页缓存(page cache)。当我们将 aof_buf 的内容写到磁盘上时,此时数据并没有真正的落盘,而是在page cache 中,为了将 page cache 中的数据真正落盘,需要执行 fsync / fdatasync 命令来强制刷盘。这边的文件同步做的就是刷盘操作,或者叫文件刷盘可能更容易理解一些。

 

在文章开头,我们提过 serverCron 时间事件中会触发 flushAppendOnlyFile 函数,该函数会根据服务器配置的 appendfsync 参数值,来决定是否将 aof_buf 缓冲区的内容写入和保存到 AOF 文件。

 

appendfsync 参数有三个选项:

1always:每处理一个命令都将 aof_buf 缓冲区中的所有内容写入并同步到AOF 文件,即每个命令都刷盘。

 

2everysec:将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,如果上次同步 AOF 文件的时间距离现在超过一秒钟,那么再次对 AOF 文件进行同步,并且这个同步操作是异步的,由一个后台线程专门负责执行,即每秒刷盘1次。

 

3no:将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,但并不对 AOF 文件进行同步,何时同步由操作系统来决定。即不执行刷盘,让操作系统自己执行刷盘。

 

AOF 的优点

1AOF RDB可靠。你可以设置不同的fsync 策略:noeverysec always。默认是everysec,在这种配置下,redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据。

 

2AOF文件是一个纯追加的日志文件。即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机等等), 我们也可以使用 redis-check-aof 工具也可以轻易地修复这种问题。

 

3)当 AOF文件太大时,Redis 会自动在后台进行重写:重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写是绝对安全,因为重写是在一个新的文件上进行,同时 Redis 会继续往旧的文件追加数据。当新文件重写完毕,Redis会把新旧文件进行切换,然后开始把数据写到新文件上。

 

4AOF 文件有序地保存了对数据库执行的所有写入操作以Redis 协议的格式保存,因此 AOF 文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。如果你不小心执行了 FLUSHALL 命令把所有数据刷掉了,但只要 AOF 文件没有被重写,那么只要停止服务器,移除 AOF 文件末尾的 FLUSHALL 命令,并重启 Redis 就可以将数据集恢复到FLUSHALL 执行之前的状态。

 

AOF 的缺点

1)对于相同的数据集,AOF 文件的大小一般会比 RDB 文件大。

 

2)根据所使用的 fsync 策略,AOF 的速度可能会比RDB 慢。通常 fsync 设置为每秒一次就能获得比较高的性能,而关闭fsync 可以让 AOF 的速度和 RDB 一样快。

 

3AOF 在过去曾经发生过这样的 bug :因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。(举个例子,阻塞命令 BRPOPLPUSH 就曾经引起过这样的 bug 。虽然这种bug AOF 文件中并不常见,但是相较而言,RDB 几乎是不可能出现这种 bug 的。

 

相关源码在aof.c,核心方法是:feedAppendOnlyFileflushAppendOnlyFile

 

 

 

混合持久化的实现原理、优缺点

 


描述:混合持久化并不是一种全新的持久化方式,而是对已有方式的优化。混合持久化只发生于 AOF 重写过程。使用了混合持久化,重写后的新AOF 文件前半段是 RDB 格式的全量数据,后半段是 AOF 格式的增量数据。

 

整体格式为:[RDB file][AOF tail]

 

开启:混合持久化的配置参数为 aof-use-rdb-preamble,配置为 yes 时开启混合持久化,在redis 4 刚引入时,默认是关闭混合持久化的,但是在 redis 5 中默认已经打开了。

 

关闭:使用 aof-use-rdb-preamble no配置即可关闭混合持久化。

 

混合持久化本质是通过 AOF 后台重写(bgrewriteaof 命令)完成的,不同的是当开启混合持久化时,fork 出的子进程先将当前全量数据以 RDB 方式写入新的AOF 文件,然后再将 AOF 重写缓冲区(aof_rewrite_buf_blocks)的增量命令以AOF 方式写入到文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的AOF 文件替换旧的的 AOF 文件。

 

优点:结合RDB AOF 的优点, 更快的重写和恢复。

 

缺点:AOF 文件里面的 RDB 部分不再是AOF 格式,可读性差。

 

相关源码在aof.c,核心方法是:rewriteAppendOnlyFile

 

 

为什么需要 AOF 重写

 


AOF 持久化是通过保存被执行的写命令来记录数据库状态的,随着写入命令的不断增加,AOF 文件中的内容会越来越多,文件的体积也会越来越大。

 

如果不加以控制,体积过大的 AOF 文件可能会对 Redis 服务器、甚至整个宿主机造成影响,并且 AOF 文件的体积越大,使用 AOF 文件来进行数据还原所需的时间就越多。

 

举个例子,如果你对一个计数器调用了 100 INCR 那么仅仅是为了保存这个计数器的当前值, AOF 文件就需要使用 100 条记录。

 

然而在实际上,只使用一条 SET 命令已经足以保存计数器的当前值了,其余99 条记录实际上都是多余的。

 

为了处理这种情况, Redis 引入了 AOF 重写:可以在不打断服务端处理请求的情况下, AOF 文件进行重建(rebuild)。

 

 

AOF 重写


 

描述:Redis生成新的 AOF 文件来代替旧 AOF 文件,这个新的AOF 文件包含重建当前数据集所需的最少命令。具体过程是遍历所有数据库的所有键,从数据库读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令。

 

命令:有两个Redis 命令可以用于触发 AOF 重写,一个是 BGREWRITEAOF 、另一个是 REWRITEAOF 命令;

 

开启:AOF重写由两个参数共同控制,auto-aof-rewrite-percentage auto-aof-rewrite-min-size,同时满足这两个条件,则触发 AOF 后台重写BGREWRITEAOF

1.  // 当前AOF文件比上次重写后的AOF文件大小的增长比例超过100

2.  auto-aof-rewrite-percentage 100

3.  // 当前AOF文件的文件大小大于64MB

4.  auto-aof-rewrite-min-size 64mb

 

关闭:auto-aof-rewrite-percentage 0,指定0的百分比,以禁用自动AOF重写功能。

auto-aof-rewrite-percentage 0

 

REWRITEAOF:进行 AOF 重写,但是会阻塞主进程,服务器将无法处理客户端发来的命令请求,通常不会直接使用该命令。

 

BGREWRITEAOFfork 子进程来进行AOF 重写,阻塞只会发生在 fork 子进程的时候,之后主进程可以正常处理请求。

 

REWRITEAOF BGREWRITEAOF 的关系与 SAVE BGSAVE 的关系类似。

 

相关源码在aof.c,核心方法是:rewriteAppendOnlyFile

 

 

AOF 后台重写存在的问题

 


AOF 后台重写使用子进程进行从写,解决了主进程阻塞的问题,但是仍然存在另一个问题:子进程在进行 AOF 重写期间,服务器主进程还需要继续处理命令请求,新的命令可能会对现有的数据库状态进行修改,从而使得当前的数据库状态和重写后的 AOF 文件保存的数据库状态不一致。

 

 

如何解决 AOF 后台重写存在的数据不一致问题

 


为了解决上述问题,Redis 引入了 AOF 重写缓冲区(aof_rewrite_buf_blocks),这个缓冲区在服务器创建子进程之后开始使用,当 Redis 服务器执行完一个写命令之后,它会同时将这个写命令追加到AOF 缓冲区和 AOF 重写缓冲区。

 

这样一来可以保证:

1、现有 AOF 文件的处理工作会如常进行。这样即使在重写的中途发生停机,现有的AOF 文件也还是安全的。

2、从创建子进程开始,也就是 AOF 重写开始,服务器执行的所有写命令会被记录到AOF 重写缓冲区里面。

 

这样,当子进程完成 AOF 重写工作后,父进程会在 serverCron 中检测到子进程已经重写结束,则会执行以下工作:

1、将 AOF 重写缓冲区中的所有内容写入到新 AOF 文件中,这时新AOF 文件所保存的数据库状态将和服务器当前的数据库状态一致。

2、对新的 AOF 文件进行改名,原子的覆盖现有的 AOF 文件,完成新旧两个 AOF 文件的替换。

 

之后,父进程就可以继续像往常一样接受命令请求了。

 

相关源码在aof.c,核心方法是:rewriteAppendOnlyFileBackground

 

 

AOF 重写缓冲区内容过多怎么办

 


AOF 重写缓冲区的内容追加到新 AOF 文件的工作是由主进程完成的,所以这一过程会导致主进程无法处理请求,如果内容过多,可能会使得阻塞时间过长,显然是无法接受的。

 

Redis 中已经针对这种情况进行了优化:

1、在进行 AOF 后台重写时,Redis 会创建一组用于父子进程间通信的管道,同时会新增一个文件事件,该文件事件会将写入 AOF 重写缓冲区的内容通过该管道发送到子进程。

2、在重写结束后,子进程会通过该管道尽量从父进程读取更多的数据,每次等待可读取事件1ms,如果一直能读取到数据,则这个过程最多执行1000次,也就是1秒。如果连续20次没有读取到数据,则结束这个过程。

 

通过这些优化,Redis尽量让 AOF 重写缓冲区的内容更少,以减少主进程阻塞的时间。

 

到此,AOF后台重写的核心内容基本告一段落,通过一张图来看下其完整流程。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2天前
|
缓存 NoSQL 关系型数据库
深入浅出Redis(四):Redis基于RDB、AOF的持久化
深入浅出Redis(四):Redis基于RDB、AOF的持久化
|
3天前
|
NoSQL 算法 关系型数据库
Redis持久化 RDB & AOF
Redis持久化 RDB & AOF
10 0
|
16天前
|
存储 运维 NoSQL
|
22天前
|
NoSQL MongoDB Redis
Python与NoSQL数据库(MongoDB、Redis等)面试问答
【4月更文挑战第16天】本文探讨了Python与NoSQL数据库(如MongoDB、Redis)在面试中的常见问题,包括连接与操作数据库、错误处理、高级特性和缓存策略。重点介绍了使用`pymongo`和`redis`库进行CRUD操作、异常捕获以及数据一致性管理。通过理解这些问题、易错点及避免策略,并结合代码示例,开发者能在面试中展现其技术实力和实践经验。
311 8
Python与NoSQL数据库(MongoDB、Redis等)面试问答
|
1月前
|
缓存 NoSQL Java
面试官:Redis如何实现延迟任务?
延迟任务是计划任务,用于在未来特定时间执行。常见应用场景包括定时通知、异步处理、缓存管理、计划任务、订单处理、重试机制、提醒和数据采集。Redis虽无内置延迟任务功能,但可通过过期键通知、ZSet或Redisson实现。然而,这种方法精度有限,稳定性较差,适合轻量级需求。Redisson的RDelayedQueue提供更简单的延迟队列实现。
363 9
|
1月前
|
缓存 NoSQL 定位技术
深入探索Redis:面试中必须掌握的关键知识点
深入探索Redis:面试中必须掌握的关键知识点
|
1月前
|
存储 安全 Java
大厂面试题详解:java中有哪些类型的锁
字节跳动大厂面试题详解:java中有哪些类型的锁
59 0
|
2天前
|
Java
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
11 0
|
2天前
|
安全 Java 程序员
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
6 0
|
17天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
42 1