标题:【Redis19】Redis进阶:持久化策略
Redis进阶:持久化策略
从最早接触 Redis 开始,我们就知道它是一个内存数据库,这是它的优势,也是它的劣势。为啥这么说呢?内存速度快,但是断电或者重启即丢。然而,要做为一个生产环境所能使用的数据库系统,将数据持久化就成为了一个必要的能力。毕竟我们可不想重启 Redis 之后,每个生产缓存都要从头重建一遍,最好是重启的时候直接就帮我们将之前已经保存的数据重新加载进来。这就是 Redis 持久化要干的事。
在 Redis 中,提供了两种持久化方案,一个叫 RDB ,一个叫 AOF 。
RDB
其实之前在基础相关的命令学习中,我们就已经接触过一点相关的知识了,比如说 SAVE 这个命令。当时我们测试的时候,在客户端执行 SAVE 马上就会把当前系统中的数据保存到一个 dump.rdb 的文件中。这个文件会保存在配置文件 dir 属性所配置的目录中。比如在我的电脑上是 dir "/usr/local/var/db/redis"
这个目录。
➜ redis ll /usr/local/var/db/redis/dump.rdb
-rw-r--r-- 1 zhangyue admin 103B 6 13 12:52 /usr/local/var/db/redis/dump.rdb
同时,这个文件名也是可以修改的,在配置文件中的 dbfilename "dump.rdb"
就是用于修改 RDB 持久化的文件名。这个文件如果你直接打开,会是乱码的,也就是说,Redis 在对 RDB 进行持久化的时候进行了压缩编码(LZF压缩 rdbcompression 配置可以设置成不压缩)。这两个配置是可以通过 CONFIG SET 动态修改的,比如说磁盘满了或者有问题了,可以马上加一块新硬盘,然后动态修改,下次持久化时就会将数据保存到新修改的路径或文件名上了。RDB 的特点主要是:
RDB 的持久化是保存整个实例中所有的数据,如果我们的程序运行时间比较长,承担的业务也比较重的话,原样保存出来的文件就会非常大。因此,RDB 在保存的时候会进行压缩编码。
RDB 文件是一个单一的并且非常紧凑的文件,因此,它很好备份,拷贝走就好啦!同样的,相比于 AOF 来说,RDB 的恢复速度也更快一些。
RDB 在保存文件时会通过 Redis 实例 fork 出一个子进程来进行,是异步的,对应的是我们之前学习过的 BGSAVE 这个命令。如果我们在命令行使用 SAVE 命令,那么会产生阻塞,因此,最好不要直接使用 SAVE 命令,使用 BGSAVE 命令或者通过配置文件让应用实例自动备份就好了。
在主从架构中,RDB 可以支持重新启动和故障切换后的部分重新同步。
有经验的小伙伴一定发现了,这货不就是 全量备份 嘛,或者换个更通俗点的名词 快照(snapshotting) 。既然是这样,那么全量备份的一些问题它也有。比如说:
频繁的 fork 子进程,特别是数据量比较大的时候,fork 过程也是非常耗时的。虽说是一个子进程,但不可避免的也会对主进程产生影响,毕竟会抢占 CPU 时间。
由上,包括 MySQL 也是类似的,我们不会一直总是做全量备份,往往是在一定的时间间隔内进行全量备份。而在时间间隔的中间如果发生了意外情况导致服务宕机的话,这中间的数据是会丢失的。
幸好,AOF 就是来弥补它的这两个问题的。下一小节我们再讲 AOF ,先来看看默认的 RDB 配置是怎样的,我们可以如何修改。
RDB 配置
在默认的配置文件中,你可以找到下面这样的配置信息。
save 3600 1
save 300 100
save 60 10000
这是啥意思,怎么有三条配置?这些数字又是啥意思?咱们一个一个来看。
save 时间 改动型命令执行次数
看到这个注释就好理解了吧,在多长时间内,执行了多少命令,就执行一次 BGSAVE 保存 RDB 文件。注意,这个执行的命令一定是改动了数据的命令,比如 SET ,而 GET 这类的命令是不算在内的。为啥呢?查询类的命令没啥可保存的必要呀,我们要保存的是存在的数据以及被改动的数据。
然后,上面写了三条,就是这三条规则有一条触发了就会进行 RDB 持久化操作。
save 3600 1:表示1小时内有1条改动命令
save 300 100:表示300秒内有100条改动命令
save 60 10000:表示60秒内有10000条改动命令
好吧,来试试。现在新加一条规则,60秒内有1条命令就保存,然后重启 Redis 实例。现在去 SET 一条测试数据,看看 dump.rdb 文件有没有变化。
注意,CONFIG SET 是不能动态修改 save 配置的。要关闭 RDB 机制应该怎么配置呢?直接 save ""
就可以啦。
AOF
Append-only file ,这是 AOF 的全称,意思也很明显,只追加操作到文件去。也就是说,它会将 SET 之类的命令追加到一个文件的末尾,然后重启实例的时候,重放这个文件中的命令就可以了。
之前其实我们也已经用过了,直接在配置文件中打开 appendonly 并设置为 yes 就启用 AOF 功能了,不过默认情况下,它是关闭的。我们也可以通过 appendfilename "appendonly.aof"
来设置文件的名称,使用的路径也是 dir 的路径,所以一般情况下,这个文件会和 dump.rdb 文件放在一起。
开启之后,我们可以直接打开这个文件看看,它的内容是我们可以看懂的哦。
➜ redis redis-cli
127.0.0.1:6379> set bb 123123
OK
127.0.0.1:6379> lpush list 1 2 3 4 5
(integer) 5
先执行两条命令,然后查看 appendonly.aof 文件。
➜ redis vim /usr/local/etc/redis.conf
REDIS0009ú redis-ver^E6.2.6ú
redis-bitsÀ@ú^EctimeÂìú§bú^Hused-mem°8^Q^@ú^Laof-preambleÀ^Aþ^@û^C^@^NÀ^A^A^Q^Q^@^@^@^N^@^@^@^C^@^@õ^Bô^Bóÿ^@^BaaÂóà^A^@^@^AbÁÞ^@ÿd´^\åØ^ßX*2^M
$6^M
SELECT^M
$1^M
0^M
*3^M
$3^M
set^M
$2^M
bb^M
$6^M
123123^M
*7^M
$5^M
lpush^M
$4^M
list^M
$1^M
1^M
$1^M
2^M
$1^M
上面乱码的不用管,我们后面再说,先看下面的。我们可以看到 set 、lpush 之类的内容,上面的数字表示执行的命令的字符长度。接着就是命令的参数,同样也是一个字符长度加上一个实际的值。这个就是 AOF 生成的持久化文件的内容。
上面的一堆乱码是啥呢?咱们先来说说这个。
AOF 重写
从上面的例子中可以看出,AOF 是原样保存数据的,这样会有一个问题那就是无用命令的增多。比如说多次执行 INCR ,但最后我们需要的其实是最后那次 INCR 的数据,在回放数据的时候其实只要 SET 到最后一次 INCR 的值就好了。
➜ redis redis-cli
127.0.0.1:6379> INCR cc
(integer) 1
127.0.0.1:6379> INCR cc
(integer) 2
127.0.0.1:6379> INCR cc
(integer) 3
127.0.0.1:6379> INCR cc
(integer) 4
127.0.0.1:6379>
// appendonly.aof
$4^M
INCR^M
$2^M
cc^M
*2^M
$4^M
INCR^M
$2^M
cc^M
*2^M
$4^M
INCR^M
$2^M
cc^M
*2^M
$4^M
INCR^M
$2^M
cc^M
现在使用 BGREWRITEAOF 命令,执行 AOF 文件重写,你就会发现上面的 INCR 命令合并成了一个 SET 命令。
// appendonly.aof
SET
$2
cc
$1
4
是不是好很多了?不够不够,要知道,AOF 一般是做增量备份的,数据增加是很快的,能不能再压缩一些呢?这时你应该想到 RDB 的压缩格式了吧,去配置文件开启 aof-use-rdb-preamble yes
,启用 RDB+AOF 的混合模式,这时再执行 BGREWRITEAOF ,你就会发现数据被压缩成了和 RDB 一样的格式,体积再进一步缩小,恢复时的执行速度也更快了。
BGREWRITEAOF 对应的自动重写配置也在配置文件中。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
下面的 64mb 表示第一次 aof 文件重写的基准大小,上面的 100 表示的是当前日志文件与 64mb 这个基准进行比较的百分比。当文件达到了这个百分比后,自动进行重写,也就是文件到达 128mb 的时候进行重写。之后的重写就不会依赖于 64mb 这个数值了,而是变成一个动态值将当前压缩后的大小保存到实例中,下次只要超过这个值的 1 倍,就会再次进行重写。如果把百分比设置为 0 ,则相当于关闭了重写的机制。
AOF 写入时机
上面 RDB 的写入时机是多选的,默认三种情况下有一个触发就会写入。而 AOF 则只能配置一种,它的配置项是这样的。
# appendfsync always
appendfsync everysec
# appendfsync no
很明显,默认情况下是每秒写入,always 是一直写入,也就是只要有一个修改类的命令出现,马上就进行写入,一直有磁盘操作,大家就能想到,它多少会拖慢速度,但很安全,因为数据基本不会丢。最后一个是不直接写入,先把日志写到内存缓冲区,然后由操作系统决定何时将缓冲区内容写回磁盘。
不用多说了吧,always 会很耗性能,no 的主动权不在 Redis 手中但性能最好。所以,一般情况下走默认的就好了,但是,注意,这1秒内如果执行了命令,但 AOF 还没有自动写入就出问题崩溃了,这时的数据也会丢失的。
AOF 重写工作原理
和 RDB 一样,好像上面 RDB 的写入过程也没细说,那就一起说吧。它们俩都是使用的写时复制这种技术。都是 fork 子进程,将新的 AOF 内容写入临时文件。在写入时,父进程对新来的命令写入到缓存。当重写工作完成后,临时文件替换成正式文件,子进程向父进程发送信号,然后父进程将缓存中的数据写入到新的 AOF 文件。
AOF 的优劣
对于 AOF 的优劣来说,很明显就是和 RDB 的对比,相对于 RDB 来说,AOF 可以:
更加安全,可以使用不同的策略,默认就是按秒的,要丢也是丢一秒内的数据
只追加文件,没有别的操作,速度比较快
自动重写 AOF 文件,优化文件体积
保证有序写入,未重写前的数据是人类可读的
当然,它也会带来一些问题。
一般来说,AOF 的文件大小会大于 RDB
虽然写入文件的速度很快,但是,在巨量写入的情况下,肯定还是不如 RDB ,特别是设置 always 之后
如果在重写过程中有新的写入,AOF 可能会使用大量内存,并且这些新的写入命令会被写入磁盘两次
awlays 会阻塞主进程不是异步的,而每秒 fsync 虽说是异步线程,但如果数据量非常大,会阻塞 fsync 子线程进而影响主线程,因此,AOF 总体来说是有阻塞问题的
用哪个?
这个嘛,要看具体的业务情况咯。如果说你想要可以媲美关系型数据库的存储安全性,那就把 AOF 和 RDB 都打开。不过官方更推荐的是,如果你只是将 Redis 当做缓存工具的话,那就都不要开。
当然,这是两种极端情况,大部业务场景下,其实我们保持默认,也就是 RDB 的默认持久化规则就好了。
另外,在服务实例启动的时候,如果开启了 AOF ,则优先根据 AOF 来进行数据还原。AOF 的优先级是要高于 RDB 的,毕竟它更安全,记录的数据更全面一些。
备份策略
对于 Redis 的这两个文件来说,备份相当的简单。为啥这么说呢?因为这两个文件即使在服务运行期间也是可以直接复制剪切走的。换句话说,你拿宝塔做个定时任务去备份这俩文件,然后再发送走就好了,本地硬盘存储也行,邮件也行,网盘也行,其它服务器直接 scp 也行,总之非常简单。
要还原的话,直接拷贝回配置文件中 dir 指定的目录位置就好了。
RDB 和 AOF 修复工具
根据上面的内容,我们知道 AOF 和 RDB 文件都是可以直接拷贝、删除、修改的,那么这两个文件就很容易就会不小心被修改或者篡改。于是,Redis 顺道便提供了两个修复工具。我们先拿 AOF 文件试试,直接乱写一些内容。
$3^M
setdfdfsdf^M
$3^M
ddd^M
$3^M
123^M
把 set 命令给随便加了些字符,这样的话如果重启服务,直接就会报出错误信息。
39166:M 15 Jun 2022 09:53:51.755 * Reading the remaining AOF tail...
39166:M 15 Jun 2022 09:53:51.755 # Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>
从提示中就可以看出,它让我们尝试用修复工具去修复 AOF 文件。那么我们就来试试。
➜ redis redis-check-aof --fix appendonly.aof
The AOF appears to start with an RDB preamble.
Checking the RDB preamble to start:
[offset 0] Checking RDB file --fix
[offset 26] AUX FIELD redis-ver = '6.2.6'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1655177336'
[offset 67] AUX FIELD used-mem = '1128240'
[offset 83] AUX FIELD aof-preamble = '1'
[offset 85] Selecting DB ID 0
[offset 103] Checksum OK
[offset 103] \o/ RDB looks OK! \o/
[info] 1 keys read
[info] 0 expires
[info] 0 already expired
RDB preamble is OK, proceeding with AOF tail...
0x 86: Expected \r\n, got: 6466
AOF analyzed: size=164, ok_up_to=126, ok_up_to_line=8, diff=38
This will shrink the AOF from 164 bytes, with 38 bytes, to 126 bytes
Continue? [y/N]: y
Successfully truncated AOF
修复成功之后再看看,它会直接把我们刚刚乱改的命令给删掉,也就是无效的内容会被清理掉,保证其它数据的正常加载。(重启之后的实例中 ddd 这条数据也就没有了)
同样的,对于 RDB 文件来说,也有一个 redis-check-rdb
命令用于修复 RDB 文件,这个命令不需要使用参数,直接就可以对指定的文件进行修复。
Redis7 的变化
在 Reids7 中,AOF 产生了比较多的变化,来自官方文档,我这里没有安装 Redis7 所以没有进行详细的测试,大家可以了解下。
采用多部分 AOF 机制,原始的单个 AOF 被分为一个基本文件和可能有多个的增量文件
基本文件是重写 AOF 时存在的初始数据,也就是 RDB 那种格式的,增量文件包含上次创建基本文件以来的增量数据,它们会放在一个目录中并由另外一个清单文件来进行跟踪
通过临时清单文件跟踪基本文件和增量文件,当它们就绪时,执行原子替换操作
加入重写限制机制,确保重试失败的重写会产生过多的增量文件的问题
总结
这一把学完,相信你对 Redis 的持久化策略应该有了比较深入的印象了吧。还是那句话,大部分情况下,用默认的配置就好了,有特殊需求的时候,至少你得知道这两种策略可以应用在什么场景下。另外就是应付面试了,这些内容也是非常容易出现在面试题中的。
参考文档:
https://redis.io/docs/manual/persistence/
视频链接
微信文章地址:https://mp.weixin.qq.com/s/s0jMUsDCqHcq9Plc1DoRvA