图解Redis持久化底层实现原理——RDB/AOF

背景

Redis作为一个内存数据库,数据是以内存为载体存储的,即所有数据都保存在内存中。一旦Redis服务器进程退出或宕机,即便重启redis服务,数据也会全部丢失。为了解决这个问题,Redis提供了持久化机制,说白了就是把数据保存到磁盘上,当redis重启后,可以从磁盘中恢复数据!

★ Redis提供2种持久化方案:

  • RDB   将在内存中的数据库记录定期生成快照存入磁盘中
  • AOF   将Reids的操作日志以追加的方式写入文件,

一个是快照的方式,一个是类似日志追加的方式

什么是RDB?

RDB 就是 Redis DataBase 的缩写,中文名为快照/内存快照。RDB是一种快照存储持久化方式,原理就是将Redis某一时刻的内存数据写入到RDB文件中,并保存到磁盘上。


该文件是一个压缩过的二进制文件,默认文件名为dump.rdb,通过该文件可以还原redis数据库的数据。在Redis服务器启动时,会重新加载dump.rdb文件的数据到内存当中,达到恢复数据目的。

 ★ 可根据 redis.conf 配置文件中设置rdb文件名 和 保存目录:

# 指定本地数据库文件名(rdb文件的名称),默认值为dump.rdb
dbfilename dump.rdb

# 指定本地数据库存放目录(dump.rdb文件存放目录),rdb、aof文件也会写在这个目录
dir /usr/local/var/db/redis/

——既然RDB持久化的方式是生成RDB文件,那么RDB文件是怎么生成的呢?

触发RDB文件生成的方式:

  • 手动触发:通过命令手动生成快照
  • 自动触发:通过配置参数的设置触发自动生成快照

★ 手动触发

执行save 和 bgsave命令,可以手动触发快照,生成RDB文件

1、save 

当某个客户端发送 save 命令后,此时会阻塞当前Redis服务器,在RDB文件创建完成之前是不能处理其他客户端发送的任何命令请求,如果数据量太大会造成长时间阻塞,期间redis无法处理其他请求,线上环境不建议使用。如下图所示:

图解Redis持久化底层实现原理——RDB/AOF

2、bgsave 

当某个客户端发送 bgsave 命令后,Redis服务器会执行fork() 函数创建一个子进程,由子进程负责rdb文件创建和写入,期间服务器不会阻塞,可以接受其他客户端的命令请求,但在fork执行阶段,服务器还会阻塞,无法接受其他客户端命令(一般时间很短!)如下图所示:

图解Redis持久化底层实现原理——RDB/AOF

 ✸ 具体流程如下:

  1. Redis客户端执行bgsave命令 或 自动触发bgsave命令;
  2. 父进程先判断:当前是否在执行save,或bgsave/bgrewriteaof(aof文件重写命令)的子进程
  3. 如果存在:则父进程直接返回。 如果不存在:父进程执行fork操作创建一个子进程,fork过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令
  4. fork完毕后返回 “ Background saving started” 信息并不再阻塞父进程,并可响应其他命令
  5. 子进程创建临时RDB文件,待数据写入完毕后,对原有文件进行原子替换;
  6. 同时子进程退出,并发送信号给父进程表示完成rdb持久化,父进程更新统计信息

注意:bgsave/bgrewriteaof(rdb/aof) 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。

✶ 简化后的流程可分为三步:

  1. 服务器接收bgsave命令,主线程需调用系统的 fork() 函数,构建一个子进程去操作;
  2. 子线程创建好RDB文件并退出时,向父进程发送信号,告知RDB文件创建完毕;
  3. 父进程接收子进程创建好的RDB文件,bgsave命令执行结束

图解Redis持久化底层实现原理——RDB/AOF

Redis服务器在处理bgsave采用子线程进行IO写入,而主进程仍然可以接收其他请求,但forks子进程是同步阻塞的,在forks子进程阶段,不能接收其他请求,如果forks一个子进程花费的时间太久(一般是很快的),bgsave命令仍然有阻塞其他客户的请求的情况发生

★ 自动触发

在以下几种情况时会自动触发生成RDB文件:

  1. redis.conf 配置文件中达到save参数条件
  2. 主从复制时,从节点要从主节点进行全量复制时也会触发bgsave,生成快照发送到从节点
  3. 执行shutdown,如果没有开启aof持久化,会触发bgsave
  4. 执行 flushall (生成空的dump.rdb!)

特别注意:flushall 是删库跑路,生成一个空的dump.rdb文件,目的是覆盖并删除上次备份产生的dump.rdb,防止重启Redis时,数据又会恢复到上一次备份的时候的数据!!

——代码/命令演示触发rdb文件自动生成

shutdown命令

1. 首先,先删除rdb文件!

连接redis客户端使用 config get dir 命令,获取rdb文件的保存路径,再打开新的命令窗口,删除 rdb文件(也可以直接删除,确保没有此文件即可)

图解Redis持久化底层实现原理——RDB/AOF

 2. 删除完rdb文件后,使用shutdown命令断开客户端连接,会自动触发rdb持久化,如下图:

图解Redis持久化底层实现原理——RDB/AOF

3. redis重启后,会重新加载dump.rdb文件的数据到内存当中,达到恢复数据目的!

 图解Redis持久化底层实现原理——RDB/AOF

▶ redis.conf配置文件save参数

redis.conf 配置文件不懂的,可以翻阅我之前redis栏目中关于配置文件的详解,附有每个参数的中文详细说明!

# 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
# 语法:save <seconds> <changes>
 
save 900 1       # 900秒(15分钟)内至少1个key值改变
save 300 10      # 300秒(5分钟)内至少10个key值改变
save 60 10000    # 60秒(1分钟)内至少10000个key值改变

1. 跟 shutdown 命令展示的一样,先删除dump.rdb文件!再打开redis.conf 文件进行如下配置:

图解Redis持久化底层实现原理——RDB/AOF

2. 打开redis服务,并用命令连接客户端,设置3个key,让其触发save条件

图解Redis持久化底层实现原理——RDB/AOF

3. 等待60秒后,查看dump.rdb文件目录:

图解Redis持久化底层实现原理——RDB/AOF

4.等dump.rdb文件生成完毕后,断开客户端,并再次重连客户端,测试数据是否恢复!

图解Redis持久化底层实现原理——RDB/AOF

▶ flushall命令

flushall 生成的是空dump.rdb文件,目的是覆盖并删除上次备份的快照,防止redis重启时,自动载入并恢复到上一份备份数据!

图解Redis持久化底层实现原理——RDB/AOF

☁ 思考:可以每秒做一次快照吗?

在未达到save的触发条件,此时服务器宕机,则会丢失最后一次同步的时间段内的数据,那是否可以设置save,每几秒或每秒就触发备份条件,不就能大大减少数据的丢失吗?

 bgsave执行不阻塞主线程,但频繁地执行全量快照,也会带来两方面的开销:

  1. 磁盘压力大,多个快照竞争有限的磁盘带宽,前一个快照还未做完,后一个又开始了,易造成恶性循环
  2. bgsave 子进程需通过 fork 操作从主线程创建出来。子进程在创建后不会再阻塞主线程,但fork执行过程本身会阻塞主线程,如果频繁 fork 出 bgsave 子进程,就会频繁阻塞主线程


 

RDB边备份边接受其他写请求的原理?

bgsave备份时如数据量太大,内存中的数据同步到硬盘的过程会持续比较长的时间,在这段时间Redis服务一般还会收到其他客户端的写操作请求。那么如何保证数据一致性呢?

RDB中的核心思路是Copy-on-Write

在正常的快照操作中,Redis主进程会fork一个子进程来处理rdb文件的写入,此时Redis服务还会接受其他客户端包括写请求在内的任何命令。

在子进程执行备份阶段,和redis主进程接受其他客户端写请求阶段,这段时间发生的数据变化会以副本的方式存放在另一个新的内存区域,待快照操作结束后才会同步到原来的内存区域。

★ 工作流程如下所示:

图解Redis持久化底层实现原理——RDB/AOF

☛ 解析:

  1. 如果主线程对A是读操作,那么主线程和 bgsave 子进程相互不影响
  2. 如果主线程对C是写操作,那么C就会被复制到另一块内存中,生成副本C,bgsave子进程会副本C的数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据C。


 

什么是AOF?

AOF(append only file)持久化:与RDB存储某个时刻的快照不同,AOF是将客户端的每一个写操作命令都记录到日志中,追加到后缀为aof 的文件末尾,在Redis服务器重启时,会加载并运行aof文件的所有命令,以达到恢复数据的目的。

AOF文件是怎么生成的呢?

  • 手动触发: bgrewriteaof 命令(该命令会重新aof,下面有讲)
  • 自动触发:根据配置规则,当然自动触发的整体时间还跟Redis的定时任务频率有关系

★ 手动触发,如下图所示:

图解Redis持久化底层实现原理——RDB/AOF

★ 自动触发

默认情况下,Redis是没有开启AOF的,可以通过配置 redis.conf 文件来开启AOF持久化:

#---------------------------1.写入---------------------------
# appendonly参数开启AOF持久化  no:关闭(默认)  yes:开启
appendonly no
 
# 指定aof文件名,默认为appendonly.aof
appendfilename "appendonly.aof"
 
 
# AOF持久化三种同步策略:
#   1) no:Redis服务器不负责写入aof,由操作系统来处理何时写入aof。性能好,不安全(不推荐)
#   2) always:每个写操作都会追加到appendonly.aof,可靠性高,数据基本不丢失(慢,安全)
#   3) everysec:每秒同步一次到appendonly.aof,会导致丢失这1s数据(折中选择,默认值)
appendfsync everysec

AOF工作流程图解如下:

图解Redis持久化底层实现原理——RDB/AOF

 解析:

  1. 所有的写命令追加到aof_buf缓冲区中。
  2. AOF会根据对应的策略向磁盘做同步操作。刷盘策略由appendfsync参数决定。

AOF持久化策略的效率与安全性:

  • always:把每个写命令都立即同步到aof文件,很慢,但是很安全
  • everysec:每秒同步一次,Redis官方推荐。
  • no:redis服务器不执行写入磁盘,而是交给OS系统来处理,非常快,但是也最不安全

命令演示操作

1. 首先打开redis.conf 配置文件,在附加模式这栏中,修改appendonly属性为yes

配置文件设置完毕后,记得重启redis服务 !!!或者先关闭服务才修改配置文件!

图解Redis持久化底层实现原理——RDB/AOF

 2. 把目录下的aof 和 rdb文件都删除掉(不知道目录的可以通过config get dir 获取)

图解Redis持久化底层实现原理——RDB/AOF图解Redis持久化底层实现原理——RDB/AOF

3. 重启服务后,重新连接客户端,执行写入操作 

图解Redis持久化底层实现原理——RDB/AOF

4.  查看appendonly.aof 文件!

每一个写入命令都被追加到aof文件中!

图解Redis持久化底层实现原理——RDB/AOF

☁ 思考:如何恢复数据?

其实想要从这些文件中恢复数据,只需要重新启动Redis即可。如图:

图解Redis持久化底层实现原理——RDB/AOF

解析:在上图中,通过重启服务,达到恢复数据的目的,成功获取到了数据!
 

——那么恢复后,我们继续执行set 写入操作呢?再次查看 aof 文件:

 图解Redis持久化底层实现原理——RDB/AOF

 结论:可以看到,写入操作又被添加到aof 的末尾,而之前已经恢复的数据的写入命令也还在!

持久化恢复数据

数据的备份、持久化做完了,我们如何从这些持久化文件中恢复数据呢?如果一台服务器上有既有RDB文件,又有AOF文件,该加载谁呢?如下图:

图解Redis持久化底层实现原理——RDB/AOF

解析:启动时会先检查AOF文件是否存在,如果不存在就尝试加载RDB

那么为什么会优先加载AOF呢?因为AOF保存的数据更完整,通过上面的分析我们知道AOF基本上最多损失1s的数据。

什么是AOF重写?

开启AOF后,每一次写操作都会被追加到appendonly.aof,随着时间推移,AOF文件会越来越大。数据恢复也更加慢。如果不加以控制,会对Redis服务器,甚至对操作系统造成影响。为了解决AOF文件体积膨胀的问题,Redis提供AOF文件重写机制来对AOF文件进行“瘦身”。

举例说明:比如对一个key多次执行incr命令

incr num 1
incr num 2
incr num 3
incr num 4
...
incr num 100000

aof 模式会把每次命令都追加到appendonly.aof 文件中,文件过大,redis服务器重启恢复数据时,就会非常慢。如果此时对aof文件重写,可以生成一个恢复当前数据的最少命令集,比如上面的例子中那么多条命令,可以重写为:

set num 100000

如何触发重写机制?

 自动触发:在redis.conf配置文件中,进行配置如下参数:

#---------------------------2.重写---------------------------

# 触发重写配置
# 当AOF文件的体积>64MB,且文件的体积比上次重写后的体积大了一倍(100%),会执行bgrewriteaof
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
 
# 如aof文件被损坏,是否开启自动修复
aof-load-truncated yes

如果要手动触发,可直接调用bgrewriteaof命令

★ 图解触发重写机制后的整体流程如下:

图解Redis持久化底层实现原理——RDB/AOF

流程如下:

  1. 判断当前是否存在正在执行 bgsave/bgrewriteaof的子进程,如果存在则bgrewriteaof命令直接返回,如果存在bgsave命令则等bgsave执行完成后再执行。
  2. 父进程执行fork操作创建子进程,fork过程中父进程是阻塞的(等同RDB中的bgsave),fork完毕后父进程才能继续响应其他客户端的请求
  3. 重写采用写入时复制,父进程fork出一个子进程,并把父进程那一时刻的内存数据拷贝一份副本给子进程,子进程对着这份副本数据逐一写入到新aof文件,子进程重写过程中,由于父进程还可响应其他客户端命令,因此子进程重写期间,父进程接受的新的写命令都存到AOF重写缓冲区aof_rewrite_buf中,防止新AOF文件生成期间丢失这部分数据。所以Redis的写命令同时追加到aof_buf 和 aof_rewirte_buf 两个缓冲区。
  4. 子进程写完新的AOF文件后,向父进程发送完成信号
  5. 父进程再把AOF重写缓冲区aof_rewrite_buf的数据,追加写入到子进程已完成的新AOF文件中,保证新AOF文件所保存的数据库状态和服务器当前状态一致。
  6. 使用新的AOF文件替换老文件,完成AOF重写。

思考:父进程为什么同时向 aof_buf 和 aof_rewrite_buf  两个缓冲区写入数据?

答:在AOF重写日志期间发生宕机(即子进程重写过程中发生意外),新aof文件还没来得及替换旧的,故恢复数据时,用的还是旧的aof文件,即写入aof_buf 缓冲区的数据。

aof_rewrite_buf 缓冲区:重写期间,由于父进程还可接受其他客户端的写请求,新接受的写请求,需放到重写缓存区aof_rewrite_buf中,子进程重写完毕,父进程会把aof_rewrite_buf重写缓冲区的数据追加到新aof文件中,确保数据的统一


 

★ 简化后的流程如下:

图解Redis持久化底层实现原理——RDB/AOF

  • 主线程fork出一个子进程重写aof 文件
  • 子进程重写完毕,主线程把子进程重写期间,其他客户端产生的写请求,追加aof文件
  • 替换旧文件
  1. AOF的rewrite重写 和 RDB的bgsave命令 都是由父进程fork 出一个子进程来执行
  2. 重写是直接把当前内存的数据生成对应命令,并不是读取旧AOF文件进行命令合并


☁ 思考:重写过程中,主进程哪些地方会阻塞?

  • fork子进程时,需要拷贝虚拟页表,会对主线程阻塞。
  • 主进程有bigkey写入时,操作系统会创建页面的副本,并拷贝原有的数据,会对主线程阻塞。
  • 子进程重写日志完成后,主进程追加aof重写缓冲区时可能会对主线程阻塞。


☁ 思考:为什么AOF重写不复用旧AOF文件

  • 父子进程写同一个文件会产生竞争问题,影响父进程的性能。
  • 如果AOF重写过程中失败了,相当于污染了原本的AOF文件,无法做恢复数据使用


AOF文件损坏怎么办?

redis 启动服务如果存在aof 文件就不会去加载rdb 文件了,而是以aof 文件来恢复数据,如果aof 文件损坏了,那会出现什么样的结果呢?

通常aof 文件错误有两种:

  1. 写入错误:在写入的过程中,出现写入操作被打断,磁盘空间不足,或者磁盘出现物理故障等问题。

  2. 停机故障:在写入的过程中,发生 Redis 服务器进程别强制杀死,或者服务器的宿主机器被强制停机等情况

★ 假设我们对已经存在的aof 文件做人工损坏

1. 使用 vim 命令进入编辑aof 文件

我是Mac系统用的终端(命令同Linux系统),所以下面讲解就用命令方式操作,windowns用户用cmd,或者找到目录直接鼠标右键打开

图解Redis持久化底层实现原理——RDB/AOF

2. 按 “ i ” 键进入编辑模式,出现 insert 字样表示当前可编辑

 图解Redis持久化底层实现原理——RDB/AOF

 3. 将正确的值搞乱之后,再退出保存如下:

 图解Redis持久化底层实现原理——RDB/AOF

4. 切换到redis 客户端,断开连接,重连服务和客户端

图解Redis持久化底层实现原理——RDB/AOF

✦ 解析:如上图所示,提示连接失败!如果在启动 Redis 时, 用户试图将带有不完整数据的 AOF 文件提供给 Redis 时, Redis 将会拒绝使用用户所提供的 AOF 文件来还原数据, 因为错误的 AOF 文件会破坏数据的一致性。

► 那么针对数据不完整aof 文件如何修复呢?

Redis 附带的 redis-check-aof 可以解决这类因为 AOF 出错而无法启动的问题: 它可以移除 AOF 文件所包含的不完整的数据, 使得 Redis 可以重新载入 AOF 文件中的数据。

1、具体操作步骤如下:

  •  cd 命令进入aof 文件所在目录
  • 使用 redis-check-aof --fix [aof文件名称]
  • 按 “ y ” 键确认

图解Redis持久化底层实现原理——RDB/AOF

程序会定位到出错的地方, 并询问用户, 是否要对文件进行截断, 如果选择 y 的话, 就会对指定的 AOF 文件进行修复。 

2、修复成功之后,我们再来用vim 命令查看aof 文件的内容

图解Redis持久化底层实现原理——RDB/AOF

可以看到,出错的原因 —— 那个不完整的 SET 命令已经被删除了。

3、测试结果

图解Redis持久化底层实现原理——RDB/AOF

RDB vs AOF

方式 RDB AOF
启动优先级
体积
恢复速度
数据安全性 会丢数据 由策略决定
轻重

RDB的优缺点有哪些?

✓ 优点

  • 恢复数据快
  • 体积小;LZF算法进行压缩,压缩后的文件体积远小于内存大小,适合备份、全量复制等场景
  • 通过RDB进行数据备,由于使用子进程生成,对Redis服务器性能影响较小。

✕ 缺点

  • 实时性不够,无法做到秒级的持久化;如果服务器宕机,还未达到save的触发条件服务器发生死机,RDB的方式会造成此时间段的数据丢失。
  • 使用save命令会造成服务器阻塞,直接数据同步完成才能接收后续请求。
  • 使用bgsave都需fork子进程,数据量太大,易造成阻塞时间过长,forks子进程会耗费内存
  • RDB文件是二进制的,没有可读性

AOF的优缺点有哪些?

✓ 优点

  • 数据安全性高,不易于丢失数据
  • AOF 文件有序地保存了对数据库执行的所有写入操作, 可读性强

✕ 缺点

  • AOF方式生成的文件太大,即使通过AFO重写,文件体积仍然很大。
  • 恢复数据的速度比RDB慢

如何选择RDB和AOF?

  • 数据不那么敏感,且可以从其他地方重新生成补回的,则可以关闭持久化
  • 数据比较重要,不想再从其他地方获取,且可承受部分数据丢失,比如缓存等,则用RDB
  • RDB持久化与AOF持久化可以同时存在,配合使用
  • 自己制定策略定期检查Redis的情况,然后可以手动触发备份、重写数据;

总结

✦ RDB持久化

在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

RDB 是存放数据库中数据,适合做数据备份,但数据可能不全,最近几分钟的数据可能没有

✦ AOF持久化

以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录。

每秒执行一次,如果有写操作的命令就存储起来,最多丢失1秒的数据,适合做数据恢复。不适合做数据备份,由于每秒都会执行多少会抢占redis的内存,影响性能。


 

上一篇:Redis系列(五)-Redis的持久化(一篇文章让你了解Redis的RDB和AOF持久化)


下一篇:redis如何关闭,开启持久化