Redis的持久化机制

Redis共有2种持久化方式:RDBAOF

 

一、RDB 持久化(Redis默认的持久化方式)

Redis 所生成的 RDB 文件是一个压缩的二进制文件,通过该文件可以还原 RDB 文件时的数据库状态。(数据库状态是指 Redis 服务器的非空数据库以及他们键值对的统称)

1、 RDB 文件的创建

我们可以通过 save 或者 bgsave 命令生成 RDB 文件,两者的区别在于: save 会阻塞 Redis 服务器进程,直到 RDB 文件创建完成为止,而在阻塞期间,服务器不能处理任何命令请求; bgsave 则不会阻塞服务器进程,因为它是通过 fork 一个子进程,并让其去创建 RDB 文件,而服务器进程(父进程)则继续处理其它命令请求。

当写完数据库状态后,新的 RDB 文件就会原子地替换旧的 RDB 文件。


提问:如果在执行 BGSAVE 期间,客户端发送 SAVE、BGSAVE 或 BGREWRITEAOF 命令给服务端,服务端会如何处理呢?

答案:在执行 BGSAVE 期间,上述三个命令都不会被执行。

详细原因:前两个会被直接拒绝,原因是为了避免父子进程同时执行两个 rdbSave 调用,防止产生竞争条件。 而 BGREWRITEAOF 命令则是会被延迟到 BGSAVE 命令执行之后再执行。

扩展:反过来,如果是 BGREWRITEAOF 命令正在执行,此时客户端发送 BGSAVE 命令则会被拒绝。

详细原因:因为 BGREWRITEAOF 和 BGSAVE 都是由子进程执行的,所以在操作方面没有冲突的地方,不能同时执行的原因是性能上的考虑——并发出两个子进程,并且这两个子进程都会同时执行大量 io(磁盘写入)操作。 

 

2、 RDB 文件的载入

RDB 文件的载入是在服务器启动时自动执行的,所以没有用于载入的命令,期间会阻塞主进程。

只要没有开启 AOF 持久化功能,在启动时检测到有 RDB 文件,就会自动载入。

提问:那如果开启了 AOF 功能呢?

详细原因:当服务器有开启 AOF 持久化功能时,服务器将会优先使用 AOF 文件来还原数据库状态。原因是 AOF 文件的更新频率通常比 RDB 文件的更新频率高。

 

3、 自动间隔性保存

在 Redis 的配置文件,有提供设置服务器每隔多久时间来执行 bgsave 命令。

Redis 默认是如下配置:

  • save 900 1    // 900 秒内,对数据库至少修改 1 次
  • save 300 10
  • save 60 10000

只要满足上面其中一种情况,服务器就会执行 bgsave 命令。

 

二、 AOF 持久化

RDB 持久化通过保存数据库状态来持久化。而 AOF 与之不同,它是通过保存对数据库的写命令来记录数据库状态

比如执行了 set key 123 , Redis 就会将这条写命令保存到 AOF 文件中。在服务器下次启动时,就可以通过载入和执行 AOF 文件中保存的命令,来还原服务器关闭前的数据库状态了。

总体流程和 RDB 持久化一样,都是创建一个文件,然后在服务器下次启动时就载入这个文件来还原数据。

1、 AOF 持久化实现

AOF 持久化分为 3 个步骤: 命令追加文件写入文件同步

命令追加:将 写命令 追加到 AOF 缓冲区的末尾。

文件写入:将 缓冲区 内容写到 AOF 文件中。

文件同步:将 AOF 文件保存到 磁盘 中。

Redis 服务器进程就是一个事件循环,这个循环中的文件事件(socket 的可读可写事件)负责接收客户端的命令请求,以及向客户端发送命令结果。因为服务器在处理文件事件时,可能会发生写操作,使得一些内容会被追加到 AOF 缓冲区末尾。所以,在服务器每次结束一个事件循环之前 ,都会调用 flushAppendOnlyFile 方法。这个方法执行以下两个工作:

WRITE:根据条件,将缓冲区内容写入到 AOF 文件。

SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

两个步骤都需要根据一定的条件来执行,而这些条件由 Redis 配置文件中的 appendfsync 选项来决定的,一共有三个选择:

  • appendfsync always    // 每执行一个命令保存一次
  • appendfsync everysec    // 每一秒钟保存一次(默认,推荐)
  • appendfsync no    // 不保存

appendfsync always :每次执行完一个命令之后, WRITE 和 SAVE 都会被执行  。

appendfsync everysec :SAVE 原则上每隔一秒钟就会执行一次。

appendfsync no :每次执行完一个命令之后, WRITE 会执行,SAVE 都会被忽略,只会在以下任意一种情况中被执行:

  • Redis 被关闭
  • AOF 功能被关闭
  • 系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行。完成依赖 OS 的写入,一般为 30 秒左右一次)

因为阻塞操作会让 Redis 主进程无法持续处理请求, 所以一般说来, 阻塞操作执行得越少、完成得越快, Redis 的性能就越好。

appendfsync no 的保存操作只会在AOF 关闭或 Redis 关闭时执行, 或者由操作系统触发, 在一般情况下, 这种模式只需要为写入阻塞, 因此它的写入性能要比后面两种模式要高, 当然, 这种性能的提高是以降低安全性为代价的: 在这种模式下, 如果运行的中途发生停机, 那么丢失数据的数量由操作系统的缓存冲洗策略决定。

appendfsync everysec 在性能方面要优于appendfsync always, 并且在通常情况下, 这种模式最多丢失不多于 2 秒的数据, 所以它的安全性要高于模式 appendfsync no , 这是一种兼顾性能和安全性的保存方案。

appendfsync always 的安全性是最高的, 但性能也是最差的, 因为服务器必须阻塞直到命令信息被写入并保存到磁盘之后, 才能继续处理请求。

而对于操作特性来分析的话,则是如下情况:

模式WRITE 是否阻塞主进程SAVE 是否阻塞主进程停机时丢失的数据
appendfsync always阻塞阻塞最多丢失一个命令的数据
appendfsync everysec阻塞不阻塞一般情况下丢失不超过 2 秒钟的数据
appendfsync no阻塞阻塞操作系统最后一次对 AOF 文件触发 SAVE 操作之后的数据

既然 AOF 持久化是通过保存写命令到文件的,那随着时间的推移,这个 AOF 文件记录的内容就越来越多,文件体积也就越来越大,对其进行数据还原的时间也就越来越久。针对这个问题,Redis 提供了 AOF 文件重写功能。

注意:

对于 appendfsync everysec ,在实际运行中, 程序在这种模式下对 fsync 或 fdatasync 的调用并不是每秒一次, 它和调用 flushAppendOnlyFile 函数时 Redis 所处的状态有关。每当 flushAppendOnlyFile 函数被调用时, 可能会出现以下四种情况:

子线程正在执行 SAVE ,并且:

  1. 这个 SAVE 的执行时间未超过 2 秒,那么程序直接返回,并不执行 WRITE 或新的 SAVE 。
  2. 这个 SAVE 已经执行超过 2 秒,那么程序执行 WRITE ,但不执行新的 SAVE 。注意,因为这时 WRITE 的写入必须等待子线程先完成(旧的) SAVE ,因此这里 WRITE 会比平时阻塞更长时间。

子线程没有在执行 SAVE ,并且:

  1. 上次成功执行 SAVE 距今不超过 1 秒,那么程序执行 WRITE ,但不执行 SAVE 。
  2. 上次成功执行 SAVE 距今已经超过 1 秒,那么程序执行 WRITE 和 SAVE 。

根据以上说明可以知道, 在“每一秒钟保存一次”模式下, 如果在情况 1 中发生故障停机, 那么用户最多损失小于 2 秒内所产生的所有数据。如果在情况 2 中发生故障停机, 那么用户损失的数据是可以超过 2 秒的。

 

2、 AOF 重写

通过该功能来创建一个新的 AOF 文件来替换旧文件。并且两个文件所保存的数据库状态一样,但新文件不会包含任何冗余命令,所以新文件要比旧文件小得多。

这个重写功能是通过读取服务器当前的数据库状态来实现的。虽然叫做「重写」,但实际上并没有对旧文件进行任何读取修改。

比如旧文件保存了对某个 key 有 4 个 set 命令,经过重写之后,新文件只会记录最后一次对该 key 的 set 命令。因此说新文件不会包含任何冗余命令。

因为重写涉及到大量 IO 操作,所以 Redis 是用子进程来实现这个功能的,否则将会阻塞主进程。该子进程拥有父进程的数据副本,可以避免在使用锁的情况下,保证数据的安全性。

子进程在重写过程中,服务器还在继续处理命令请求,新命令可能会对数据库进行修改,这会导致当前数据库状态和重写后的 AOF 文件,所保存的数据库状态不一致

为了解决这个问题,Redis 设置了一个 AOF 重写缓冲区。在子进程执行 AOF 重写期间,主进程需要执行以下三个步骤:

  • 执行客户端的请求命令
  • 将执行后的写命令追加到 AOF 缓冲区
  • 将执行后的写命令追加到 AOF 重写缓冲区

当子进程结束重写后,会向主进程发送一个信号,主进程接收到之后会调用信号处理函数执行以下步骤:

  • 将 AOF 重写缓冲区内容写入新的 AOF 文件中。此时新文件所保存的数据库状态就和当前数据库状态一致了
  • 对新文件进行改名,原子地覆盖现有 AOF 文件,完成新旧文件的替换

当函数执行完成后,主进程就继续处理客户端命令。

因此,在整个 AOF 重写过程中,只有在执行信号处理函数时才会阻塞主进程,其他时候都不会阻塞

 

三、 如何选择持久化方案

官方建议:如果你要想提供很高的数据保障性,那么建议你同时使用两种持久化方式。如果你可以接受灾难带来的几分钟的数据丢失,那么你可以仅使用 RDB

在数据恢复方面:RDB 的启动时间会更短,原因有两个:

  • RDB 文件中每一条数据只有一条记录,不会像 AOF 日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。
  • RDB 文件的存储格式和 Redis 数据在内存中的编码格式是一致的,不需要再进行数据编码工作,所以在 CPU 消耗上要远小于 AOF 日志的加载。  

注意:上面说了 RDB 快照的持久化,在进行快照的时候(save),fork 出来进行 dump 操作的子进程会占用与父进程一样的内存,真正的 copy-on-write,对性能的影响和内存的耗用都是比较大的。

比如机器 8G 内存,Redis 已经使用了 6G 内存,这时 save 的话会再生成 6G,变成 12G,大于系统的 8G。这时候会发生交换;要是虚拟内存不够则会崩溃,导致数据丢失。所以在用 redis 的时候一定对系统内存做好容量规划。    

目前,通常的设计思路是利用复制(Replication)机制来弥补 aof、snapshot 性能上的不足,达到了数据可持久化。即 Master 上 Snapshot 和 AOF 都不做,来保证 Master 的读写性能,而 Slave 上则同时开启 Snapshot 和 AOF 来进行持久化,保证数据的安全性。 

 

四、 总结

1、RDB 持久化是 Redis 默认持久化方式,通过保存数据库键值对来记录状态来持久化,由 SAVE 和 BGSAVE 命令来创建 RDB 文件。前者阻塞 Redis 主进程,后者不会。

2、RDB 可以在配置文件设置每隔多久时间来执行 BGSAVE 命令: save 900 1 、 save 300 10 、 save 60 10000 

3、AOF 通过追加写命令来保存当前数据库状态。其持久化功能的实现可以分为 3 个步骤:命令追加(到 AOF 缓冲区)、文件写入(缓冲区内容写到 AOF 文件)、文件同步(AOF 文件保存磁盘)

4、其中文件同步和保存可以通过配置文件的 appendfsync 选项来决定: appendfsync always 、 appendfsync everysec 、 appendfsync no

5、为了解决 AOF 文件越来越大的问题,Redis 提供了 AOF 重写功能,并且不会阻塞主进程。

6、为了解决 AOF 重写过程中,新 AOF 文件所保存的数据库状态和当前数据库状态可能不一致的问题,Redis 引入了 AOF 重写缓冲区,用于保存子进程在重写 AOF 文件期间产生的新的写命令。

7、最后是官方对于两种持久化方式选择的一些建议。

 

参考:Redis 设计与实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值