Redis 笔记-持久化(Persistence)

1,776 阅读8分钟

mindmap

Redis 持久化机制解决了单机数据存储和备份问题(宕机后能恢复),支持 RDB 和 AOF 两种方式。持久化并不是说读写会和磁盘交互, 读写都是全内存的,持久化数据只是作为磁盘备份,实例重启或者机器断电的时候可以从磁盘加载到内存。

  • rdb 是 redis 默认的持久化策略
  • 支持完全禁用持久化(注释所有的 save <seconds> <changes> 或者配置 save ""
  • AOF、RDB 两种模式可以同时开启,!!注意!!在这种情况下如果 redis 重启了,优先选择 AOF 文件来重新构建原始数据集,因为 AOF 文件所保存的数据通常是最完整

下面来详细地了解两种方式:

RDB 简介

RDB 是 某一个时刻的内存镜像数据(snapshotting:快照) 写入磁盘文件。该方式的优点是持久化后的文件比较小(只有某一个时刻的数据被压缩),实例重启的时候加载会更快。缺点是如果实例重启,备份时刻之后写入的数据将会丢失。

可以通过调用 SAVE 或者 BGSAVE 命令,手动让 redis 进行数据保存工作。

在每次做快照文件时,是以 fork 子进程的方式,如果数据特别大,可能会导致对客户的服务暂定数毫秒甚至几秒。工作流程如下:

  1. redis fork 子进程
  2. 子进程尝试将数据 dump 到临时的 RDB 快照文件中
  3. 完成快照,替换之前的快照文件

RDB 的快照什么时候会被触发?

可在配置文件中按照实际需求设定,默认如下:

save 900 1
save 300 10
save 60 10000

意思为同时满足以上三个条件中的一个就会被触发

  • 900 秒中(15分钟)至少有 1 个 key 发生了变化
  • 300 秒中(5分钟)至少 10 个 key 发生了变化
  • 60 秒钟至少 10000 个 key 发生了变化

当然也可以自己定义触发策略,格式:save <seconds> <changes>。当只存在 save "" 策略或者直接注释掉默认的三个策略即可关闭 RDB

一些注意点

  • dumb.rdb 每次生成一个新的快照,都会覆盖之前的老快照
  • 在使用 cli shutdown 命令后,会立即生成一份完整 RDB 快照

AOF 简介

AOF (append only file)是将客户端发送到 Redis 写入命令追加到磁盘文件。根据配置的刷盘策略不同,实例重启会丢掉的数据量也不一样。现在有下面三种方式(在配置文件中指定):

  • appendfsync=always 每条写入都会刷盘,最多只会丢失当前正在写入的命令
  • appendfsync=everysec 每秒刷一次盘,最多丢失一秒的数据,此种为官当推荐且默认策略
  • appendfsync=no 不显式刷盘。(不是不刷盘而是由操作系统来决定何时刷盘:linux 大部分是 30s)。可能会丢失刷盘之前的写入数据。

该持久化方式的优点:若发生故障丢失的数据会比 rdb 少,相对安全。 缺点:在写入比较多的场景下,产生的 aof 文件通常会比 rdb 文件大,从而会导致 重启时加载数据慢。举个例子,如果对 key 做 1000 次 incr, 则 aof 文件则会记录 1000 次 incr,而 rdb 只存储 1000 这个值即可。不过 aof 允许 rewrite, 比如把例子里面的 1000 次 incr a 变成一次 incr a 1000 命令,在下文会讲到。

AOF 在某些 fsync 策略(如:always)下性能会成倍下降,因为每次写入需要往 aof 日志中追加一条记录。

通常,在将 fsync 设置为每秒(everysec)的情况下,性能仍然很高,并且在禁用 fsync 的情况下,即使在高负载下,它也应与 RDB 一样快。 但在巨大的写负载情况下,RDB 仍然能够提供有关最大延迟的更多保证。

log rewriting

redis 中存储的数据是有限的,一部分可能会自动过期,也可能会被用户删除,可能会被 redis 的淘汰策略删除,但对应的写操作命令还保存在 aof 的日志文件中,所以该文件会不断地增长。

redis 采用 rewrite 操作,如当前日志文件中存放了针对 100w 数据的写日志,但实际 redis 中只存放了 10w 的数据。当进行 rewrite 操作时,redis 会基于当前 10w 数据重新构建出一个 aof 日志文件,并覆盖老的 aof 文件,该操作不会影响对客户端的服务。从而确保日志文件不会过于庞大。

  1. redis fork 一个子进程
  2. 子进程基于当前内存中的数据,构建日志,开始往一个新的临时的AOF文件中写入日志
  3. redis 主进程,接收到 client 新的写操作之后,在内存中写入日志,同时新的日志也继续写入旧的 AOF 文件
  4. 子进程写完新的日志文件之后,redis 主进程将内存中的新日志再次追加到新的 AOF 文件中
  5. 用新的日志文件替换掉旧的日志文件

什么时候 Redis 会进行 log rewrite 操作?

参照模板配置文件中的解释如下:

# This is how it works: Redis remembers the size of the AOF file after the
# latest rewrite (if no rewrite has happened since the restart, the size of
# the AOF at startup is used).
#
# This base size is compared to the current size. If the current size is
# bigger than the specified percentage, the rewrite is triggered. Also
# you need to specify a minimal size for the AOF file to be rewritten, this
# is useful to avoid rewriting the AOF file even if the percentage increase
# is reached but it is still pretty small.

翻译过来就是,配置文件中的 auto-aof-rewrite-percentage、auto-aof-rewrite-min-size 两个配置项用来指定 redis 自动触发 rewriting 的时机,两个配置的默认值分别是 100、64mb。当触发 rewriting 操作必须满足两个条件,一当前文件大小必须大于 64 兆,二当前文件大小比最新那一次重写后文件大小至少大了一倍(100%)

RDB、AOF 模式联系

如何在不重启的情况下从 RDB 切换到 AOF

注:需要 Redis 2.2 版本以上

  1. 为最新的 dump.rdb 文件创建一个备份。
  2. 将备份放到一个安全的地方。
  3. 执行以下两条命令:
## 开启了 AOF 功能: Redis 会阻塞直到初始 AOF 文件创建完成为止
## 之后 Redis 会继续处理命令请求, 并开始将写入命令追加到 AOF 文件末尾
redis-cli> CONFIG SET appendonly yes
## 执行的第二条命令用于关闭 RDB 功能(可选)
redis-cli> CONFIG SET save ""
  1. 确保命令执行之后,数据库的键的数量没有改变。
  2. 确保写命令会被正确地追加到 AOF 文件的末尾。

RDB 和 AOF 之间的相互作用

  1. RDB 在执行 SHAPSHOTTING 操作时,不会执行 aof rewrite 操作。反之不会执行 RDB SHAPSHOTTING
  2. RDB 在执行 SHAPSHOTTING,此时用户执行 BGREWRITEAOF 命令,只有等 RDB 快照生成以后,才会去执行 AOF
  3. 同时有 RDB shapshotting 文件和 aof 日志文件,那么在 redis 重启的时候,会优先使用 aof 进行数据恢复,因为其中的日志更加完整。

数据备份

RDB 模式下会生成多个数据文件,每个数据文件代表了某一个时刻中 redis 的数据。这种模式适合做冷备,由 redis 去控制固定时长生成快照文件,再将这种完整数据文件发送到一些安全存储上去(如云服务)。当然 AOF 模式也可以做冷备。

  1. 写 crontab 定时调度脚本去做数据备份
  2. 每小时都 copy 一份 rdb 的备份,到一个目录中去,保留 48 小时的备份
  3. 每天都 copy 当日 rdb 备份,到一个目录中,保留一个月
  4. 删除太旧的备份|将备份文件都上传到云服务

每小时 copy 一次备份,删除 48 小时前的数据

> crontab -e

0 * * * * sh /usr/local/redis/copy/redis_rdb_copy_hourly.sh

redis_rdb_copy_hourly.sh 文件内容

#!/bin/sh 

cur_date=`date +%Y%m%d%k`
rm -rf /usr/local/redis/snapshotting/$cur_date
mkdir /usr/local/redis/snapshotting/$cur_date
cp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date

del_date=`date -d -48hour +%Y%m%d%k`
rm -rf /usr/local/redis/snapshotting/$del_date

每天 copy 一次备份

> crontab -e

0 0 * * * sh /usr/local/redis/copy/redis_rdb_copy_daily.sh

redis_rdb_copy_daily.sh 文件内容

#!/bin/sh 

cur_date=`date +%Y%m%d`
rm -rf /usr/local/redis/snapshotting/$cur_date
mkdir /usr/local/redis/snapshotting/$cur_date
cp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date

del_date=`date -d -1month +%Y%m%d`
rm -rf /usr/local/redis/snapshotting/$del_date

数据恢复实践

当生产环境开启了 aof,但只有 rbd 数据文件时,启动 redis 还是依旧没有恢复数据(因为 redis 认为 aof 文件较为完整,优先选择 aof 进行恢复)。此时只能先把 aof 关了,再去使用 rdb 文件进行恢复。

此时会有两种情况非常危险且需要注意,不小心就会造成数据丢失:

  • 手动触发了 SAVE 或者 BGSAVE 就危险了,原来的 rdb 文件(存有数据的)将会被覆盖
  • 在命令行执行了 shutdown 操作,此时会触发 SAVE 操作也会覆盖 rdb

当恢复数据后,先要使用命令行将 appendonly 开关打开(热修改),使 redis 基于当前恢复的数据产生相应的 aof 日志文件。确认 aof 文件生成之后,再将 redis 关闭,手动修改配置文件,打开 appendonly 开关。

# config get appendonly
# config set appendonly yes

参考