很多小伙伴都用 Redis 做缓存,那如果 Redis 服务器宕机,内存中数据全部丢失,应该如何做数据恢复呢?有人说很简单呀,直接从 MySQL 数据库再读回来就得了。
这种方式存在两个问题:一是频繁访问 MySQL 数据库,有一定的风险;二是慢,从界面上来看,从 MySQL 读就不如从 Redis 快。
远哥远哥,那咋办呀?教教我吧。
我用中指抵着小胖的下吧,说到:傻瓜,我们可以做持久化呀。Redis 的持久化分两种,一种是 AOF,另一种是 RDB。来,坐哥哥腿上,我给你好好说道说道。
老规矩,先上张脑图:
持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML 数据文件中等等。持久化是将程序数据在持久状态和瞬时状态间转换的机制。
必须声明一点:Redis 的单线程,是指 Redis 的网络 IO 和键值对读写是由一个线程(主线程)完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
基于内存
高效的数据结构
合理的线程模型
AOF(Append Only File) 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态,也就是每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾。
修改 redis.conf 配置文件,默认是 appendonly no(关闭状态),将 no 改为 yes 即可开启 AOF 持久化:
appendonly yes
在客户端输入如下命令也可,但是 Redis 服务器重启后会失效。
192.168.17.101:6379> config set appendonly yes
OK
AOF 持久化功能的实现可以分为命令追加(append)、文件写回磁盘两个步骤。
AOF 持久化功能开启时,Redis 在执行完一个写命令之后,会将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾,此时缓冲区的记录还没有写入到 appendonly.aof 文件中。
AOF 保存的是 Redis 的写命令,比如:执行命令 set testkey testvalue,它存储的内容如下图所示:
AOF 格式
其中,“*3” 表示当前命令有三个部分,每部分都是由 + 数字开头,后面紧跟着具体的命令、键或值。这里,数字表示这部分中的命令、键或值一共有多少字节。例如, 3 set 表示这部分有 3 个字节,也就是 set 命令。
AOF 记录日志的方式被称为写后日志,也就是先执行命令再记录,而 MySQL 中的 redo log、binlog 等都是写前日志。它的写入流程是下图这样的:
写 AOF 流程
写后有什么优点?
写后有什么缺陷?
你发现没有?写后的两个缺陷都是 AOF 的写入磁盘时相发生的,我们来看看它是怎么写入的呢?
AOF 提供了三个选择,也就是 AOF 配置项 appendfsync 的三个可选值。
针对避免主线程阻塞和减少数据丢失问题,这三种写回策略都无法做到两全其美。主要原因是:
三种策略的优缺点
总结一下就是:想高性能,选择 No 策略;想高可靠性,选择 Always 策略;允许数据有一点丢失,又希望性能别受太大影响,选择 Everysec 策略。
不说了,看图:
AOF 恢复数据
我不知道你发现没有?AOF 文件是不断地将写命令追加到文件的末尾来记录数据库状态的。写命令不断增加,AOF 体积也越来越大。
有些命令是执行多次更新同一条数据,但其实它是可以合并成同一条命令的。比如:LPUSH 对列表数据做了 6 次更改,但 AOF 只需要记录最后一次更改。因为日志恢复时,只需要执行最后一次更改的命令即可。
为了处理这种情况,Redis 提供了 AOF 的重写机制。它的多变一功能,把 6 条写命令合并成一条。如下所示:
LPUSH
如果你的某些键有成百上千次的修改,重写机制节约的空间就很可观了。
有两种触发的方法,一个是调用命令 BGREWRITEAOF;一个是修改配置文件参数。
# 方式一
192.168.17.101:6379> BGREWRITEAOF
Background append only file rewriting started
# 方式二
auto-aof-rewrite-percentage 100 #当前AOF文件大小和上一次重写时AOF文件大小的比值
auto-aof-rewrite-min-size 64mb #文件的最小体积
重写步骤
# 是否开启AOF功能
appendonly no
# AOF文件件名称
appendfilename "appendonly.aof"
# 写入AOF文件的三种方式
appendfsync always
appendfsync everysec
appendfsync no
# 重写AOF时,是否继续写AOF文件
no-appendfsync-on-rewrite no
# 自动重写AOF文件的条件
auto-aof-rewrite-percentage 100 #百分比
auto-aof-rewrite-min-size 64mb #大小
# 是否忽略最后一条可能存在问题的指令
aof-load-truncated yes
RDB 持久化是指在客户端输入 save、bgsave
或者达到配置文件自动保存快照条件时,将 Redis 在内存中的数据生成快照保存在名字为 dump.rdb
(文件名可修改)的二进制文件中。
save 命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,在 Redis 服务器阻塞期间,服务器不能处理任何命令请求。在客户端输入 save
127.0.0.1:6379> save
OK
快照生成完毕,会弹出 DB saved ondisk 的提示。
1349:M 25 Apr 13:16:48.935 * DB saved on disk
bgsave 执行时,主线程会创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。
127.0.0.1:6379> bgsave
Background saving started
PS:bgsave 命令执行期间 SAVE 命令会被拒绝;不能同时执行两个 BGSAVE 命令;不能同时执行 BGREWRITEAOF 和 BGSAVE 命令。
bgsave 执行时,Redis 主线程能正常读写数据。读操作时,主线程和 bgsave 子线程互不影响;写操作时,Redis 会利用写时复制技术(Copy-On-Write, COW),生成被修改数据的副本。然后 bgsave 子线程把副本数据写入 RDB。
比如,bgsave 期间,主线程修改键值对 C,过程如下:
写时复制技术
但是在这过程中发生宕机了咋办?比如,T0 时刻做了一次快照,T0+t 时刻又做了一次。但是 t 时间内主线程修改完数据 5 和 9,然后 Redis 宕机了,RDB 没记录到修改后的数据。
Redis 重启恢复数据,就会出现数据 5 和 9 丢失的情况,没办法恢复。
丢失数据
这该咋办?我们需要记住那些数据被修改了。
如下图所示,记录 t 时刻被修改的数据就需要占用额外的空间,而 Redis 是内存数据库,空间非常宝贵。所以,直接记录到内存这种方式不可取。
增量快照
内存开销比较小的方法是把 t 时间的增量写操作记录到 AOF 日志中,这样既保留了 RDB 的快速恢复,也没占用额外的空间。
如图,T1 和 T2 时刻的修改,用 AOF 日志记录,等第二次做全量快照时,清空 AOF 日志,因为此时的修改都记录到快照中了,恢复不用 AOF 日志了。
AOF 记录增量修改
庆幸的是 Redis 4.0 就开始提供了这种 RDB + AOF 的持久化方式,开启的配置项是 aof-use-rdb-preamble yes
,它需要配合 AOF 的重写机制实现。
# 开启混合持久化
redis> config set aof-use-rdb-preamble yes
OK
# AOF 重写
redis> BGREWRITEAOF
Background append only file rewriting started
在没有第二次做全量快照之前,它的格式是这样的:前半部分是 RDB 格式,后半部分是 AOF 增量日志。如果这个时候宕机,直接拿 appendonly.aof 恢复数据。
appendonly.aof 格式
数据恢复流程
本文主要讲解 Redis AOF 、RDB 持久化的原理和两者的优缺点,对比两者后,我还给你总结了 Redis 混合持久化的流程。最后给了你一些选择持久化方案的建议,希望看完你能有所收获。
全文将近字,张图,希望能帮到你。好啦,以上就是狗哥关于 Redis 持久化的总结。感谢各技术社区大佬们的付出,尤其是极客时间,真的牛逼。如果说我看得更远,那是因为我站在你们的肩膀上。希望这篇文章对你有帮助,我们下篇文章见~