张三拔掉了服务器电源之后Redis的数据是怎么恢复的?

298 阅读11分钟

大家都知道Redis是基于内存的NoSQL,而内存中的数据断电即失,恢复供电之后内存中只有一片空白,我们Redis中存储的数据也就丢失了,那我们如何恢复我们断电前的数据呢?今天我们就一起来看一下Redis持久化相关的知识点吧

首先需要明确的一点是,持久化是将我们的数据写入到磁盘中的过程,虽然Redis是基于内存的,但是持久化的过程是离不开磁盘的,为了保证断电之后可以快速恢复数据,必须要将内存中的数据写入磁盘以避免数据断电丢失。

Redis提供了两种持久化的方法,RDB和AOF

  • RDB是将内存中的Redis数据快照以二进制形式进行存储
  • AOF是将某时刻后的对Redis进行写入的命令和参数记录到AOF文件中。

RDB

redis关于RDB的配置可以通过查看Redis中的redis.conf文件进行查看,里面有一组默认的配置

save 900 1     //900S内有一条数据被修改
save 300 10    //300S内有十条数据被修改
save 60 10000  //60S内有10000条数据被修改

这组配置是触发RDB机制的三个条件,满足其中之一即可立即执行RDB持久化,当然这组配置我们也可以根据自己的需求进行修改

前面我们说过RDB文件是内存的二进制快照,我们拿到RDB文件直接打开会发现里面的数据是我们看不懂的(因为是二进制的),那么这个RDB文件怎么进行数据恢复的呢?我们接下来继续来看

RDB的BGSAVE和SAVE

RDB文件非常适合用来进行数据备份和恢复,将RDB文件直接读入内存就可以完成数据的恢复,整个过程耗时较短。

创建RDB文件有两种方式,SAVE和BGSAVE

  • SAVE方法是阻塞式的方法,Redis主进程会将内存快照写入到磁盘文件中,这个过程结束之前不会处理其他任何命令。
  • BGSAVE是非阻塞的持久化方法,通过创建子进程将当前内存中的数据写入磁盘中的RDB文件,主进程继续处理命令

BGSAVE的流程

  • 主进程收到BGSAVE命令
  • 主进程Fork创建子进程,子进程与主进程共享内存中的代码块与数据段。此时主进程继续响应执行到来的命令
  • 子进程遍历读取内存中的数据写入磁盘中的RDB文件
  • 当主进程需要进行写入和修改时,通过Copy On Write(COW)机制进行数据段页面的分离。父进程在分离出来的页上进行写入和修改,子进程继续在原来共享页面上进行遍历读取和磁盘写入,子进程
  • 子进程写入的所有数据会用临时文件替换旧的RDB文件

我们可以通过定时备份RDB文件来实现Redis数据库备份,RDB文件是经过压缩的,占用的空间会小于内存中的数据大小。

除了自动快照还可以手动发送SAVE或BGSAVE命令让Redis执行快照。通过RDB方式实现持久化,由于RDB保存频率的限制,如果数据很重要则考虑使用AOF方式进行持久化。

前面我说过触发RDB持久化的三个条件,那么我们来想象一个场景,假设t1时刻触发了一次RDB,然后再t1到t2间写入的数据未触发RDB持久化过程,那么在t1-t2间如果发生了宕机,则会丢失增量数据。那么怎么来保存这些增量数据呢?

AOF

AOF(Append Only File)是Redis持久化的另外一种方式,他通过追加某时刻之后的执行的命令(只包括对内存进行修改的命令)到AOF文件中。在我们需要恢复数据的时候,通过读取AOF文件,把AOF文件中的命令再执行一遍,就可以恢复数据。AOF文件内容可以看下面

$ cat appendonly.aof
*2        //语句的长度
$6        //参数的长度
SELECT
$1
0
*3
$3
set
$4
key1
$5
Hello
*3
$6
append
$4
key1
$7
World!
*2
$3
del
$4
key1

我们可以看到,AOF中的数据内容都是我们直接可以读懂的。我们上面也说过通过AOF的方式是通过将AOF中的数据重放已一遍就可以恢复数据。

当Redis收到来自客户端的修改指令后,会先进行参数校验、逻辑处理,如果没有问题就立即将这条命令追加到AOF文件中,也就是先执行再落盘。

我们再来想一下,如果我们的AOF文件记录了一年的命令,那么整个AOF文件都会非常大,且其中可能有一些命令已经被后来的命令覆盖掉,因此我们需要对AOF文件进行瘦身

AOF瘦身

Redis 提供了 bgrewriteaof 指令用于对 AOF 日志进行瘦身。 原理就是开辟一个子进程对内存进行遍历转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中。序列化完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了,瘦身工作就完成了。

子进程在进行 AOF 重写期间, 主进程还需要继续处理命令, 而新的命令可能对现有的数据进行修改, 会出现数据库的数据和重写后的 AOF 文件中的数据不一致。因此Redis 增加了一个 AOF 重写缓存, 这个缓存在 fork 出子进程之后开始启用, Redis 主进程在接到新的写命令之后, 除了会将这个写命令的协议内容追加到现有的 AOF 文件之外, 还会追加到这个缓存中。

当子进程重写完AOF之后会像父进程发送一个信号,父进程在接到完成信号之后,先将AOF重写缓存中的内容写入到新的AOF文件中,对新AOF文件进行改名,覆盖原来的AOF文件

fsync

Linux的glibc提供fsync可以将指定文件的内容从内存刷到磁盘中,只要Redis实时调用fsync就可以保证AOF文件不丢失,但是fsync是一个IO操作,非常耗时,因此在保证最终一致性的情况下,我们可以通过每秒执行一次fsync操作来实现性能与数据安全性之间折衷。关于fsync操作也是可以通过redis.conf文件进行配置的appendfsync选项:always、everysec和no,always表示执行每个命令都进行刷盘,会损耗性能;everysec,每秒执行一次刷盘,数据安全性与可用性的折衷方案;no不进行刷盘,保证可用性无法保证数据安全性。

触发AOF重写

每次当 serverCron 定时函数执行时, 它都会检查以下条件是否全部满足, 如果是的话, 就会触发自动的 AOF 重写:

  1. 没有 BGSAVE 命令在进行 防止于RDB的冲突
  2. 没有 BGREWRITEAOF 在进行 防止和手动AOF冲突
  3. 当前 AOF 文件大小至少大于设定值 基本要求 太小没意义
  4. 当前 AOF 文件大小和最后一次 AOF 重写后的大小之间的比率大于等于指定的增长百分比

Redis数据恢复

首先我们来说一下AOF和RDB的优缺点

  • AOF存储了全部的修改指令,保证了数据完整性,但重放AOF文件恢复数据会耗费很长时间
  • RDB是一个周期性持久化,当宕机时会丢失上一次触发RDB时到宕机前所有的数据,无法保证数据完整性,但加载RDB文件耗时少

Redis 4.0 提供了更好的混合持久化选项: 创建出一个同时包含 RDB 数据和 AOF 数据的 AOF 文件, 其中 RDB 数据位于 AOF 文件的开头, 它们储存了服务器开始执行重写操作时的数据库状态,至于那些在重写操作执行之后执行的 Redis 命令, 则会继续以 AOF 格式追加到 AOF 文件的末尾, 也即是 RDB 数据之后。于是在 Redis 重启的时候,可以先加载 RDB的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

扩展知识点:Copy On Write机制

Copy On Write(COW)机制在Redis执行BGSAVE 和BGREWRITEAOF的时候会通过COW机制提高效率,那么COW机制是甚么东西呢?我们来看一下吧

COW机制在很多方面都有应用,操作系统在创建子进程的时候可以通过COW来减少资源的分配,Java的CopyOnWriteArrayList可以更高效的保证线程安全。

在Linux创建子进程的时候我们需要通过调用Fork函数来实现,调用Fork会返回两次,将子进程的PID返回给父进程,将0返回给子进程(也有可能返回-1,说明资源不足无法创建子进程),在传统的创建子进程的过程中,会直接把父进程的数据拷贝给子进程,创建结束之后子进程和父进程的数据段和栈堆是相互独立的,也就是说子进程和父进程除了PID,其余都是一样的,但是我们创建子进程的往往是为了实现不同的功能,也就是需要执行exec()这个函数,exec()函数会替换掉当前进程的地址空间从而实现自己的功能,也就是说,有很大可能子进程并不会用到创建子进程时复制过来的数据,因为子进程实现其他功能的时候会将复制过来的数据清除掉。既然复制的这部分数据往往用不到,那么就产生了COW这项技术,通过COW,在创建子进程的时候,不需要对父进程的数据进行一次拷贝,直接将子进程与父进程共享内存空间,这样就不需要去复制一份额外的数据了。在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。

COW的原理

fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。

Copy On Write技术好处是什么?

  • COW技术可减少分配和复制大量资源时带来的瞬间延时
  • COW技术可减少不必要的资源分配。比如fork进程时,并不是所有的页面都需要复制,父进程的代码段和只读数据段都不被允许修改,所以无需复制

Copy On Write技术缺点是什么?

  • 如果在fork()之后,父子进程都还需要继续进行写操作,那么会产生大量的分页错误(页异常中断page-fault),这样就得不偿失。

欢迎大家关注我的微信公众号:码外狂徒,记录张三与后端的爱恨情仇,每周更新两篇后端知识和至少三篇LeetCode解题思路

参考链接

mp.weixin.qq.com/s?__biz=MzI…

mp.weixin.qq.com/s/f9N13fnyT…

mp.weixin.qq.com/s/O_qDco6-D…

《Redis深度历险》