【经验】Redis 数据持久化、数据备份、数据的故障恢复

缓存由于其高并发和高性能的特性,已经在项目中被广泛使用。在读取持久化,数据备份,数据的故障恢复方面你究竟了解多少呢?

1.redis 持久化的意义—-redis 故障恢复

在实际的生产环境中,很可能会遇到 redis 突然挂掉的情况,比如 redis 的进程死掉了、电缆被施工队挖了 (支付宝例子) 等等,总之一定会遇到各种奇葩的现象导致 redis 死掉,这时候放在 redis 内存中的数据就会全部丢失,这些数据可能服务很多的系统或者服务,当然,我们可以重新启动 redis,重启之后,如果 redis 没有持久化,redis 中的数据就会全部丢失。

如果通过持久化将数据搞一份到磁盘,然后定期的同步和备份到云存储服务上去,那么就可以保证数据不会全部丢失,还是可以恢复一部分数据的。

2. 持久化的两大机制 (RDB 和 AOF)

RDB:对 redis 数据执行周期性的持久化

AOF: 将每条命令写入日志,以 append-only 的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放 AOF 的写入指令来重新构建整个数据集

是否实用持久化要看具体的业务场景:

如果只是想让 redis 仅仅作为纯内存的缓存,那么可以禁止 RDB 和 AOF。

故障恢复大致思路:

通过 RDB 或 AOF,都可以将 redis 内存中的数据持久化到磁盘上来,然后可以将数据备份到阿里云,如果 redis 挂了,服务器中内存和磁盘的数据就都丢了,这时候可以将阿里云中的备份文件拷贝至指定目录下,然后重启 redis,redis 就会自动根据持久化数据文件去恢复内存中的数据,继续对外提供服务。如果同时室友了 RDB 和 AOF 两种持久化机制,那么在重启的时间建议使用 AOF 的方式重新构建数据,因为 AOF 中的数据更加完整。

3. 剖析 RDB 和 AOF

RDB:早上 7 点,这个时候 redis 中有 500 条数据,这个时候 redis 会在一定周期内生成一个 RDB 快照文件,等到了 9 点的时候 redis 中有 8000 条数据,这个时候又在一定的周期内生成了另一个 RDB 快照文件,这就是 RDB 持久化机制。

AOF:redis 中每写入一条指令,就会把这条指令更新到磁盘中的文件中。然而在现代操作系统中,写文件不是直接写磁盘,会先写进 os cache,然后在一定时间内再从 os cache 刷入 disk file,对于 AOF 来说每隔一秒 (可配置) 调用一次操作系统饿 fsync 操作强制将 os cache 中的数据刷入磁盘文件中。但是 redis 内存中的数据也不是无限增长的,它是定期的根据 LRU 算法清理一些不常用的数据,这样才能保证 AOF 不会无限增长,但是如果 LRU 的清理速度比不上 AOF 的膨胀速度的时候,这时候当 AOF 大到一定程度就会进行 AOF rewrite 操作。AOF rewrite 操作就会基于当时 redis 内存中的数据来重新构造一个更小的 AOF 文件,然后将旧的 AOF 文件删除。

简单的说,假设 redis 限定了只能存放 10G 数据,这时候不断的在 redis 中写入数据,当达到了 10G 的数据量的时候,这时候根据 LRU 清理了一些不常用的数据,清理了 5G,这时候又写了 5G,这时候 AOF 文件记录了 15G 的数据相关的写入指令,假如这个时候 AOF 已经膨胀了,这个时候 redis 进行 AOF rewrite 操作,重新生成了一个新的 10G 的数据指令的 AOF 文件,这个时候将继续写入新的 AOF 文件,将老的 AOF 文件删除。

4.RDB 和 AOF 优缺点

RDB 优点

(1).RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 redis 中的数据。

RDB 做冷备,生成多个文件,每个文件都代表了某一个时刻的完整的数据快照

AOF 也可以做冷备,只有一个文件,但是你可以,每隔一定时间,去 copy 一份这个文件出来

但是 RDB 更适合做冷备,它的优势是由 redis 去控制固定时长生成快照文件的事情,比较方便; AOF,还需要自己写一些脚本去做这个事情,需要自己写定时脚本,而且 RDB 数据做冷备,在最坏的情况下,提供数据恢复的时候,速度比 AOF 快

(2).RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis 保持高性能,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可

RDB,每次写,都是直接写 redis 内存,只是在一定的时候,才会将数据写入磁盘中

AOF,每次都是要写文件的,虽然可以快速写入 os cache 中,但是还是有一定的时间开销的, 速度肯定比 RDB 略慢一些

(3). 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速

RDB 缺点

(1). 如果想要在 redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据,这也是 rdb 最大的缺点,就是不适合做第一优先的恢复方案,如果你依赖 RDB 做第一优先恢复方案,会导致数据丢失的比较多。

(2).RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒,所以一般不要让 RDB 的间隔太长,否则每次生成的 RDB 文件太大了,对 redis 本身的性能可能会有影响的

AOF 优点

(1).AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 fsync 操作,最多丢失 1 秒钟的数据, 每隔 1 秒,就执行一次 fsync 操作,保证 os cache 中的数据写入磁盘中,redis 进程挂了,最多丢掉 1 秒钟的数据。

(2).AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。

(3).AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 rewrite log 的时候,会对其中的内容进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。

(4).AOF 日志文件的命令通过可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用 flushall 命令清空了所有数据,只要这个时候后台 rewrite 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据

AOF 缺点

(1). 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大

(2).AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync,性能也还是很高的,如果你要保证一条数据都不丢,也是可以的,AOF 的 fsync 设置成没写入一条数据,fsync 一次,那就完蛋了,redis 的 QPS 大降。

(3). 以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 /merge/ 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。

(4). 唯一的比较大的缺点,其实就是做数据恢复的时候,会比较慢,还有做冷备,定期的备份,不太方便,可能要自己手写复杂的脚本去做,做冷备不太合适

AOF 和 RDB 数据恢复机制

AOF,存放的指令日志,做数据恢复的时候,其实是要回放和执行所有的指令日志,来恢复出来内存中的所有数据的

RDB,就是一份数据文件,恢复的时候,直接加载到内存中即可

无论是 AOF 和 RDB,在 redis 中都以一个文件的形式存在!!!

5.RDB 和 AOF 如何选择

(1). 不要仅仅使用 RDB,因为那样会导致你丢失很多数据

(2). 也不要仅仅使用 AOF,因为那样有两个问题,第一,你通过 AOF 做冷备,没有 RDB 做冷备,来的恢复速度更快; 第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug

(3). 综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复

6. 如何配置 RDB 持久化

(1).redis.conf 文件,也就是 /etc/redis/6379.conf,去配置持久化

例如:save 60 1000

( 每隔 60s,如果有超过 1000 个 key 发生了变更,那么就生成一个新的 dump.rdb 文件,就是当前 redis 内存中完整的数据快照,这个操作也被称之为 snapshotting,快照

也可以手动调用 save 或者 bgsave 命令,同步或异步执行 rdb 快照生成 )

(2).save 可以设置多个,就是多个 snapshotting 检查点,每到一个检查点,就会去 check 一下,是否有指定的 key 数量发生了变更,如果有,就生成一个新的 dump.rdb 文件

7.RDB 持久化机制的工作流程

(1).redis 根据配置自己尝试去生成 rdb 快照文件,fork 一个子进程出来,子进程尝试将数据 dump 到临时的 rdb 快照文件中,完成 rdb 快照文件的生成之后,就替换之前的旧的快照文件,dump.rdb,每次生成一个新的快照,都会覆盖之前的老快照。

8. 基于 RDB 持久化机制的数据恢复实验

(1). 在 redis 中保存几条数据,立即停掉 redis 进程,然后重启 redis,看看刚才插入的数据还在不在

(2). 在 redis 中再保存几条新的数据,用 kill -9 粗暴杀死 redis 进程,模拟 redis 故障异常退出,导致内存数据丢失的场景

注意:通过 redis-cli SHUTDOWN 这种方式去停掉 redis,其实是一种安全退出的模式,redis 在退出的时候会将内存中的数据立即生成一份完整的 rdb 快照

9. 如何配置 AOF 持久化

(1).AOF 持久化,默认是关闭的,默认是打开 RDB 持久化

(2).appendonly yes,可以打开 AOF 持久化机制,在生产环境里面,一般来说 AOF 都是要打开的,除非你说随便丢个几分钟的数据也无所谓,打开 AOF 持久化机制之后,redis 每次接收到一条写命令,就会写入日志文件中,当然是先写入 os cache 的,然后每隔一定时间再 fsync 一下,而且即使 AOF 和 RDB 都开启了,redis 重启的时候,也是优先通过 AOF 进行数据恢复的,因为 aof 数据比较完整

(3). 可以配置 AOF 的 fsync 策略,有三种策略可以选择,一种是每次写入一条数据就执行一次 fsync; 一种是每隔一秒执行一次 fsync; 一种是不主动执行 fsync

always: 每次写入一条数据,立即将这个数据对应的写日志 fsync 到磁盘上去,性能非常非常差,吞吐量很低; 确保说 redis 里的数据一条都不丢,那就只能这样了

everysec: 每秒将 os cache 中的数据 fsync 到磁盘,这个最常用的,生产环境一般都这么配置,性能很高,QPS 还是可以上万的

no: 仅仅 redis 负责将数据写入 os cache 就撒手不管了,然后后面 os 自己会时不时有自己的策略将数据刷入磁盘,不可控了

10.AOF 持久化的数据恢复实验

(1). 先仅仅打开 RDB,写入一些数据,然后 kill -9 杀掉 redis 进程,接着重启 redis,发现数据没了,因为 RDB 快照还没生成

(2). 打开 AOF 的开关,启用 AOF 持久化

(3). 写入一些数据,观察 AOF 文件中的日志内容

(4).kill -9 杀掉 redis 进程,重新启动 redis 进程,发现数据被恢复回来了,就是从 AOF 文件中恢复回来的 (redis 进程启动的时候,直接就会从 appendonly.aof 中加载所有的日志,把内存中的数据恢复回来)

注意:在 appendonly.aof 文件中,可以看到刚写的日志,它们其实就是先写入 os cache 的,然后 1 秒后才 fsync 到磁盘中,只有 fsync 到磁盘中了,才是安全的,要不然光是在 os cache 中,机器只要重启,就什么都没了

11.AOF rewrite

AOF 工作原理

(1).redis fork 一个子进程

(2). 子进程基于当前内存中的数据,构建日志,开始往一个新的临时的 AOF 文件中写入日志

(3).redis 主进程,接收到 client 新的写操作之后,在内存中的数据继续写入新日志到 AOF 文件中,同时新的数据也继续写入旧的 AOF 文件

(4).redis 主进程将内存中的新写进去的日志再次追加到新的 AOF 文件中

(5). 用新的日志文件替换掉旧的日志文件

redis 中的数据其实有限的,很多数据可能会自动过期,可能会被用户删除,可能会被 redis 用缓存清除的算法清理掉,redis 中的数据会不断淘汰掉旧的,就一部分常用的数据会被自动保留在 redis 内存中,所以可能很多之前的已经被清理掉的数据,对应的写日志还停留在 AOF 中,AOF 日志文件就一个,会不断的膨胀,到很大很大,所以 AOF 会自动在后台每隔一定时间做 rewrite 操作,比如日志里已经存放了针对 100w 数据的写日志了; redis 内存只剩下 10 万; 基于内存中当前的 10 万数据构建一套最新的日志,到 AOF 中; 覆盖之前的老日志; 确保 AOF 日志文件不会过大,保持跟 redis 内存数据量一致

redis 2.4 之前,还需要手动,开发一些脚本,crontab,通过 BGREWRITEAOF 命令去执行 AOF rewrite,但是 redis 2.4 之后,会自动进行 rewrite 操作

注意:

在 redis.conf 中,可以配置 rewrite 策略

auto-aof-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb

比如说上一次 AOF rewrite 之后,是 128mb,然后就会接着 128mb 继续写 AOF 的日志,如果发现增长的比例,超过了之前的 100%,也就是 256mb,就可能会去触发一次 rewrite,但是此时还要去跟 min-size,64mb 去比较,256mb > 64mb,才会去触发 rewrite

12.AOF 破损文件的修复

如果 redis 在 append 数据到 AOF 文件时,机器宕机了,可能会导致 AOF 文件破损,用 redis-check-aof –fix 命令来修复破损的 AOF 文件。

13.AOF 和 RDB 同时工作

(1). 如果 RDB 在执行 snapshotting 操作,那么 redis 不会执行 AOF rewrite; 如果 redis 再执行 AOF rewrite,那么就不会执行 RDB snapshotting

(2). 如果 RDB 在执行 snapshotting,此时用户执行 BGREWRITEAOF 命令,那么等 RDB 快照生成之后,才会去执行 AOF rewrite

(3). 同时有 RDB snapshot 文件和 AOF 日志文件,那么 redis 重启的时候,会优先使用 AOF 进行数据恢复,因为其中的日志更完整

14. 企业级的持久化的配置策略

企业中,RDB 的生成策略,用默认的也差不多

save 60 10000:如果你希望尽可能确保说,RDB 最多丢 1 分钟的数据,那么尽量就是每隔 1 分钟都生成一个快照,低峰期,数据量很少,也没必要

AOF 一定要打开,fsync,everysec

auto-aof-rewrite-percentage 100: 就是当前 AOF 大小膨胀到超过上次 100%,上次的两倍

auto-aof-rewrite-min-size 64mb: 根据你的数据量来定,16mb,32mb

15. 企业级的数据备份方案

(1). 写 crontab 定时调度脚本去做数据备份

(2). 每小时都 copy 一份 rdb 的备份,到一个目录中去,仅仅保留最近 48 小时的备份

(3). 每天都保留一份当日的 rdb 的备份,到一个目录中去,仅仅保留最近 1 个月的备份

(4). 每次 copy 备份的时候,都把太旧的备份给删了

(5). 每天晚上将当前服务器上所有的数据备份,发送一份到远程的云服务上去

按小时和按天同时备份

每小时 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
每天一次将所有数据上传一次到远程的云服务器上去

16. 企业级数据恢复方案

(1). 如果是 redis 进程挂掉,那么重启 redis 进程即可,直接基于 AOF 日志文件恢复数据

(2). 如果是 redis 进程所在机器挂掉,那么重启机器后,尝试重启 redis 进程,尝试直接基于 AOF 日志文件进行数据恢复,前提是 AOF 没有破损,AOF append-only,顺序写入,如果 AOF 文件破损,那么用 redis-check-aof fix 修复。

(3). 如果 redis 当前最新的 AOF 和 RDB 文件出现了丢失 / 损坏,那么可以尝试基于该机器上当前的某个最新的 RDB 数据副本进行数据恢复,当前最新的 AOF 和 RDB 文件都出现了丢失 / 损坏到无法恢复,一般不是机器的故障,而是人为。

17. 容灾演练

appendonly.aof + dump.rdb,优先用 appendonly.aof 去恢复数据。

(1). 如果关闭 AOF 持久化机制,并且 dump.rdb 是有数据的,这时候重启 redis,发现内存中明显没有恢复数据。

原因:redis 启动的时候,自动重新基于内存的数据,生成了一份最新的 rdb 快照,直接用空的数据,覆盖掉了我们有数据的 dump.rdb

(2). 如果打开 AOF,停止 redis 之后,先删除 appendonly.aof,然后将我们的 dump.rdb 拷贝过去,然后再重启 redis,发现依然没有恢复数据

原因:虽然你删除了 appendonly.aof,但是因为打开了 aof 持久化,redis 就一定会优先基于 aof 去恢复,即使文件不在,那就创建一个新的空的 aof 文件

(3). 停止 redis,暂时在配置中关闭 aof,然后拷贝一份 rdb 过来,再重启 redis,这时候内存中的数据恢复成功; 假如不小心,再关掉 redis,手动修改配置文件,打开 aof,再重启 redis,数据又没了,因为是空的 aof 文件,所以所有数据又没了。

在数据安全丢失的情况下,基于 rdb 冷备,如何完美的恢复数据,同时还保持 aof 和 rdb 的双开?

(4). 停止 redis,关闭 aof,拷贝 rdb 备份,重启 redis,确认数据恢复,直接在命令行热修改 redis 配置,打开 aof,这个 redis 就会将内存中的数据对应的日志,写入 aof 文件中,此时 aof 和 rdb 两份数据文件的数据就同步了。

注意:redis config set 热修改配置参数,可能配置文件中的实际的参数没有被持久化的修改,再次停止 redis,手动修改配置文件,打开 aof 的命令,再次重启 redis

(5). 如果当前机器上的所有 RDB 文件全部损坏,那么从远程的云服务上拉取最新的 RDB 快照回来恢复数据

(6). 如果是发现有重大的数据错误,比如某个小时上线的程序一下子将数据全部污染了,数据全错了,那么可以选择某个更早的时间点,对数据进行恢复

举个例子,12 点上线了代码,发现代码有 bug,导致代码生成的所有的缓存数据,写入 redis,全部错了,找到一份 11 点的 rdb 的冷备,然后按照上面的步骤,去恢复到 11 点的数据。