为了避免进程退出而造成的数据丢失问题,Redis提供了两种持久化的机制,分别的RDB和AOF。当Redis由于异常退出而重启时,可以利用异常发生之前的持久化文件实现数据恢复。
一、RDB持久化
RDB持久化是把当前进程的数据生成快照,并且把该快照保存到磁盘中的过程。既可以通过命令手动触发RDB的持久化,也可以通过设置规则自动触发RDB的持久化。
可以通过下面命令查看其默认存放路径和文件名
config get dir
config get dbfilename
- 手动触发
通过redis-cli向redis-server发送save或者bgsave命令

执行save命令时,日志如下:

执行bgsave命令时,我们可以看到下面的日志日志:

- 自动触发
1、
通过设置save属性,在x秒内发生y次更新,则自动执行一次bgsave命令,该设置可以在redis-cli中通过下面命令查看:
config get save

另外可以同时设置多组规则,只要有其中一组满足即会触发RDB持久化,每两个数组为一组规则
m n 表示m秒内进行了n次更新 则自动执行一次bgsave
RDB持久化之后,存储路径由dir配置项指定,文件名由dbfilename配置项指定,在redis-cli中执行下面命令即可查看当前配置:
config get dir
config get dbfilename

2、
当从节点执行全量复制时,会触发主节点自动执行bgsave命令。先在slave中执行
slaveof no one
会将当前slave与原master失联并且不再是只读模式,可以在master中看到如下日志

然后通过
flushall
清空当前slave实例中的数据,然后重新通过
slave master-host master-port
连接上master,此时master和slave之间恢复连接后会进行全量复制,此时可以在master中看到下面日志,当master决定要进行全量复制后,需要先触发bgsave命令fork一个子进程去持久化rdb文件,当rdb文件dump完成时,将其发送给slave

- 被动触发
1、
当执行
debug reload
命令时,会先触发save命令dump当前内存的rdb文件,save执行完成后先清空当前redis,然后重新加载rdb文件,日志如下

2、当执行shutdown命令时,如果没有开启aof,则默认会触发save命令
可以通过
config set appendfsync no
关闭当前redis实例的aof持久化,然后执行shutdown命令,通过日志可以看到在redis实例退出前,触发了save命令

- bgsave流程
通过上面可以了解redis在什么时候会进行rdb方式的持久化操作,save命令由于是同步的所以不需要多关注,而bgsave命令是异步的,异步就代表复杂,所以需要来简单了解下bgsave生成rbd文件的流程:

- 当在父进程中执行bgsave命令时,父进程首先会检测当前是否有子进程正则工作,如果有则直接忽略当前bgsave任务
- 如果没有其他子进程则先同步阻塞fork一个子进程,可以通过
info stats
命令查看lastest_fork_usec选项来查看当前redis实例最近的一个fork动作耗时,以微秒为单位

子进程fork完成后就不再阻塞父进程,父进程可以继续处理客户端的命令
- 子进程根据当前时间点的父进程内存快照,生成rdb文件并且原子替换已有的rdb文件,可以通过
info Persistence
查看rdb_last_*选项查看最近一次save和bgsave动作的相关信息,比如时间戳和执行结果

- rbd持久化的优点
1、rbd文件存储的是二进制数据,可以通过开启lzf压缩使用远小于快照大小的磁盘空间来持久化数据
2、由于是直接存储快照,所以redis加载rdb文件速率很快
- rdb持久化的缺点
1、rdb持久化必定是全量持久化,无法增量持久化,即无法做到事实持久化
2、rdb持久化时时间一般都很长,频繁执行会消耗大量资源
3、redis版本迭代过程中,产生了许多互相不兼容的rdb格式,所以要慎重升级生产环境的redis服务器
二、aof持久化
和rdb的关注点不同,aof持久化并不关注内存快照,而是记录从aof持久化开启之后的所有更新命令,在极端情况下可以做到实时持久化。可以通过下面命令开启aof持久化:
config set appendonly yes
当aof从关闭状态到打开状态时可以看到下面日志,和bgsave生成rdb文件一样,aof持久化也是由子进程完成的
另外从日志中可以看到,当打开aof持久化时,初始的aof文件是由aof重写生成的,后面会介绍到aof重写

aof持久化文件的存放目录和文件名可以通过下面命令查看
config get dir
config get appendfilename
aof持久化本质上是一种增量备份的概念,假如当前服务器开启了aof持久化,基本过程如下图所示

当开发者通过redis客户端向redis服务器发送一个更新请求,在服务器执行完客户端请求后,会将该更新请求发送到redis-server进程的一个缓冲区中缓存起来,该缓冲区称为aof_buf,aof_buf中的数据会以某种策略以追加的模式写入到磁盘上的aof文件中。
可以通过
config get appendfsync
查看当前缓冲区中命令的落盘策略,默认为everysec

开发者可以根据需要从下面的三个策略中选择一个
- everysec
其实际意义为每次执行完更新命令时,将数据写入aof_buf,然后立即执行一次write将aof_buf写入内核,并且同步线程每秒执行一次fsync将aof文件关联的内核缓冲区中的内容落盘
- awlays
其实际意义为每次执行完更新命令时,将数据写入aof_buf,然后立即执行一次write将aof_buf写入内核,并且紧接着执行一次fsync将aof文件关联的内核缓冲区中的内容落盘
- no
其实际意义为每次执行完更新命令时,将数据写入aof_buf,然后立即执行一次write将aof_buf写入内核,剩下的工作就交给操作系统来完成了,至于内核缓冲区中与aof相关联的内容何时落盘,完全不可控,最多是30s
rdb持久化文件中存储的是二进制,而aof持久化文件中存储的是字符串,打开aof文件可以看到下面类似的内容,记录着redis-server最近执行过的更新命令

不过正是由于aof文件中存储的是字符串,所以其体积通常要比rdb文件大许多,而且速度增加的很快,为了解决这个问题,redis提供了aof重写机制。
aof重写简单来说就是将当前redis数据库中的键值对转换成语句,然后写入新的aof文件的操作,由于从当前状态转换成语句时可以进行语句合并,这样新的aof文件会比原文件小得多。比如原aof文件中有:
set a a
delete a
此时数据库中并不存在a这个key的记录,所以重写后的aof文件将不存在这两条语句
又比如原aof文件中有下面几条语句:
lpush a 11
lpush a 22
lpush a 33
数据库中此时键为a的list中有三个值,则重写后会被整合成一条语句
lpush a 11 22 33
aof的重写既可以手动触发也可以自动触发,不过他们都需要的前提是redis服务器开启了aof持久化
- 手动重写
直接向redis服务器发送bgrewriteaof命令就可以重写已有的aof文件,当手动重写aof文件时可以看到下面日志

- 自动重写
aof文件的自动重写,由redis服务器的下面两个参数共同控制:
- 1
auto-aof-rewrite-min-size
只有当前的aof文件大小大于该值时,才可能被重写

67108864 / 1024 / 1024 = 64,即默认阈值为64M,单位字节
不过至于会不会被重写还要根据另外一个的条件来决定
- 2
auto-aof-rewrite-percentage
该值表示当前aof文件大小相对于上次重写后的aof文件大小的增长百分比,即
auto-aof-rewrite-percentage = (aof_current_size - aof_base_size) / aof_base_size
可以通过
info persistence
查看这几个属性的当前值

aof_base_size就是上次重写后的aof文件大小,aof_current_size是当前aof文件的大小。
- aof持久化流程
aof持久化的基本流程如下:

我们需要注意下面几点:
- 当执行bgrewriteaof命令时,正在进行aof重写,则当前命令直接返回
- 当执行bgrewriteaof命令时,正在进行 rdb文件的dump,则会等其完成再执行bgrewriteaof
- 由于bgrewriteaof是fork出来的子进程完成的,fork使用的是写时复制技术,即只能看到fork一瞬间的数据库内容,所以为了防止bgrewriteaof任务执行时redis服务器所执行的更新命令,这些命令都会被记录到aof重写缓冲区
- 当新的aof文件生成时,可以原子替换旧的aof文件
三、通过持久化文件恢复数据
只要持有aof和rdb文件中的任何一个,都可以进行数据恢复,其基本流程如下:

从流程图中可以看到如下特性:
- 无论有没有持久化文件,redis都可以正常启动
- aof持久化文件加载的优先级要高于rdb文件加载
- rdb文件的加载日志如下

- aof文件的加载日志如下

















