深度好文:保姆级教程彻底搞懂Redis 持久化

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

为什么需要持久化?

Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘中,那么一旦服务器进程退出,服务器中的数据库状态也会丢失,数据丢失是一种很严重的生产及故障,所以需要对 Redis 数据进行持久化。 Redis 提供了如下几种不同级别的持久化方式

  • RDB 快照持久化可以在指定的时间间隔内生成内存数据集的时间点快照
  • AOF 持久化记录服务器执行的所有写命令,并在服务重启时通过命令重放来还原数据
  • 混合持久化,兼顾RDB和AOF的特性

RDB 快照

在默认情况下,Redis 将内存数据库快照保存在名为 dump.rdb 的二进制文件中。

可以通过修改 redis.conf 配置,让 Redis 在 N 秒内数据集至少有 M 个变动 这一条件被满足时,自动保存一次数据集。

# 以下设置会让 redis 在满足60秒内至少有1000个数据被改动,这一条件被满足时,自动保存一次数据集
save 60 1000
复制代码

可以设置多个规则,满足任意规则都会触发保存机制。

还可以手动执行命令生成 RDB 快照,使用客户端链接 redis server,执行命令 savebgsave 可以生产 dump.rdb 文件。每次执行命令都会将所有 redis 内存数据快照到一个新的 rdb 文件,并覆盖原有的 rdb 快照文件。

关闭 RDB 快照需要将所有的 save 策略注释掉,然后设置一个空策略 save ""

快照执行流程

  1. 调用 fork() 同时拥有附近和和子进程。
  2. 子进程将数据集写入到临时 RDB 文件中。
  3. 当子进程完成对新 RDB 文件的写入时,Redis用新的RDB文件替换原来的RDB文件,并删除旧的RDB文件。

image-20220322170706175

bgsave 的 COW(写时复制机制)

Redis 借助操作系统提供的写时复制技术(Copy-On-Write COW),在生成快照的同时,依然可以正常处理写命令。

简单来说,bgsave 是由主线程 fork 生成的,可以贡献主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。

此时,如果主线程对这些数据都是读操作,那么主线程和 bgsave 子进程相互不影响。

但是如果主线程要修改一块数据,那么这块数据就会被复制一份,生成该数据的副本。然后 bgsave 子进程会把这个副本数据写入 RDB 文件,在这个过程,主线程仍然可以直接修改原来的数据。

save 与 bgsave 的比较

命令 save bgsave
IO类型 同步 异步
是否阻塞其他命令 否(在生成子进程执行调用fork函数时会有短暂阻塞)
复杂度 O(n) O(n)
优点 不会消耗额外的内存 不阻塞客户端
取点 阻塞客户端命令 需要fork子进程,消耗内存

AOF (append-only file)

快照功能并不是非常耐久的(durable):如果 redis 因为某些原因而造成故障停机,那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。

1.1 版本开始,Redis 增加了一种完全耐久的持久化方式:AOF 持久化,将修改的每一条指令记录进文件 appendonly.aof 中(先写入到 OS cache ,每隔一段时间 fsync 到磁盘)。

可以通过修改配置文件来打开 AOF 功能:

appendonly yes
复制代码

从现在开始,每当 Redis 执行一个改变数据的命令时,这个命令就会被追加到 AOF 文件的末尾。这样的话,当 Redis 重新启动时,程序就可以通过执行 AOF 文件内的命令来达到重建数据的目的。

比如执行命令 set beifeng 666 AOF 文件会记录如下数据:

*3
$3
set
$7
beifeng
$3
666
复制代码

这是一种 resp 协议格式数据,星号后面的数字代表命令有多少个参数,$ 后面的数字代表这个参数有几个字符。

如果执行带过期时间的 set 命令,AOF 文件里记录的并不是执行的原始命令,而是记录 key 过期的时间戳。

# 执行命令
set aixuexi 666 ex 60
# AOF 文件内容
*5
$3
SET
$7
aixuexi
$3
666
$4
PXAT
$13
1647941694816
复制代码

有三个选项可以配置 Redis 多久才将数据 fsync 到磁盘。

appendfsync always # 每次有新命令追加到 AOF 文件时就执行一次 fsync,非常慢、也非常安全
appendfsync everysec # 默认值,每秒 fsync 一次,足够快,并且在故障时只会丢失 1s 的数据
appendfsync no # 从不 fsync 将数据交给操作系统来处理。更快也更不安全
复制代码

AOF 重写

因为 AOF 的运作方式是不断的将命令追加到文件的末尾,所以随着写入命令的不断增加,AOF文件的体积也越来越大,AOF 文件里可能有太多没用的指令,所以 AOF 会定期根据内存最新数据生成 AOF 文件。

127.0.0.1:6379> INCR count # 执行14次 
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
复制代码

重写后的 AOF 文件

*3
$3
SET
$5
count
$2
14
复制代码

如下两个参数可以控制 AOF 重写频率

auto-aof-rewrite-percentage 100 # aof 文件自上次重写后文件大小增长了100% 再次触发重写
auto-aof-rewrite-min-size 64mb # aof 至少要达到64m才会自动重写,文件太小恢复速度本来就很快,重写的意义不大
复制代码

AOF 重写 redis 会 fork 出一个子进程去做,不会对 redis 正常命令处理有太多影响。

AOF 执行流程

AOF 和 RDB 创建快照一样,也巧妙的利用了写时复制机制。

  1. Redis 执行 fork() ,现在同时拥有父进程和子进程
  2. 子进程开始将新 AOF 文件的内容写入到临时文件
  3. 对于所有新执行的写入命令,父进程一边将他们累积到一个内存缓冲中,一边将这些改动追加到现有 AOF 文件的末尾;这样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的
  4. 当子进程完成重写工作时,他给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾
  5. 搞定!现在 Redis 原子的用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾

image-20220323100251458

RDB 与 AOF 对比

持久方式 RDB AOF
启动优先级
体积
恢复速度
数据安全性 容易丢失数据 根据策略决定

生产环境都可以使用,redis 启动时如果既有 rdb 文件又有 aof 文件,则优先选择 aof 文件恢复数据,因为相对来说 AOF 更安全一点。

Redis 混合持久化

重启 Redis 时我们很少使用 RDB 恢复内存数据,因为会丢失大量数据。通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 RDB 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。Redis 4.0 为了解决这个问题,带来了一个新的持久化选项混合持久化

通过如下配置开启混合持久化(默认为开启状态)。

aof-use-rdb-preamble yes # 必须先开启AOF; appendonly yes
复制代码

开启混合持久化之后,AOF在重写时 不再是单纯将内存数据转换为 RESP 命令写入 AOF 文件,而是将重写这一刻之前的内存做 RDB 快照处理,并且将 RDB 快照内容和增量的 AOF 修改内存数据的命令存在一起,都写入临时 AOF 文件。等重写完成后替换原有 AOF 文件。

这样在 Redis 重启的时候,可以先加载 RDB 内容,然后在增量重放 AOF 日志,大幅度提升效率。

混合持久化 AOF 文件结构

image-20220323103453282

备份 Redis 数据

  1. 写 crontab 定时任务脚本,每小时 copy 一份 rdb 或 aof 文件的备份到一个目录去,仅仅保留48小时的备份
  2. 每天都保留一份当天的数据备份到一个目录去,可以保留最近1个月的备份
  3. 每次拷贝最近的数据的时候,删除太旧的数据
  4. 每天晚上拷贝当前机器的数据到其他机器或OSS存储上,以防机器损坏

猜你喜欢

转载自juejin.im/post/7084626876412461064