Redis 持久化机制?RDB,AOF?这下弄明白了
简单介绍
线上 Redis 被攻击了?
发生甚么事了?
这两天自己做项目,出现一个奇怪的问题。
前一天特地存在 Redis 里的数据,后一天来看就没有了,明明也没有设置过期时间。
更好玩的是 Redis 里数据有着几个「backup」,就像下图所示:
到底是备份了什么,给我数据都整没了。。。
把「backup」里的数据编码再一看:
好家伙,我一下来劲了,什么 「cleanfda」什么「init.sh」的, 这是被攻击了啊!
这样的攻击,黑客一般会删除数据库的数据,然后给你留下「backup」指引,告诉你这些被删除的数据他做了备份,然后你应该如何花钱赎你的数据。
查明被攻击的原因是 Redis 密码过于简单,而且没有隐藏端口,于是就被攻破了。。。
为什么 Redis 数据需要持久化?
补救上面的问题
重新部署 Redis 不是难事,但是数据丢了就不好玩了,线下测试阶段则需要一点一点调用接口重新构造数据,线上阶段那还可能面临更多严肃的问题。
像我这里还在本地测试,就得一点一点重新构造数据了。
为什么 Redis 需要持久化数据?
首先 Redis 是一个基于内存的数据库,那么一旦 Redis 宕机,内存中的数据将全部丢失。 于是我们需要持久化机制,将内存中的数据持久化到硬盘中去。
同时考虑到上面服务被攻击,或者数据迁移的可能性,数据持久化都可以减少损失,使 Redis 服务更加灵活。
Redis 持久化机制
Redis 主要有几种持久化机制?
Redis 提供4种持久化方案:
- RDB
- AOF
- 虚拟内存(VM)
- DISKSTORE
而真正被官方文档认可支持的持久化方式只有 RDB 和 AOF。于是本篇我们主要介绍并且实践 RDB 和 AOF 持久化机制。
RDB
什么是 RDB?
RDB 是 Redis DataBase 的缩写,可以称作快照/内存快照。同时 RDB 是 Redis 默认启用的持久化方案。
RDB 持久化就是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值。
RDB 的优点(这方面的内容需要读完 RDB 篇章才能充分理解)
- RDB文件是某个时间节点的快照,默认使用 LZF 算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景;
- Redis 加载 RDB 文件恢复数据要远远快于 AOF 方式;
RDB 的缺点(这方面的内容需要读完 RDB 篇章才能充分理解)
- RDB 方式实时性不够,无法做到秒级的持久化。如果系统发生故障将丢失最后一次创建快照以后的数据。
- RDB 方式保存快照开销较大,每次调用
bgsave
都需要fork
子进程,fork
子进程属于重量级操作,频繁执行成本较高。 - RDB 文件是二进制的,没有可读性,AOF 文件在了解其结构的情况下可以手动修改或者补全。
- 版本兼容 RDB 文件问题。
RDB 持久化的触发方式
-
手动触发
执行
save
命令-
save
命令效果阻塞当前 Redis 服务器,直到 RDB 过程完成为止。
-
阻塞时间
对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用
-
-
被动触发
执行
bgsave
命令-
bgsave
命令效果Redis 进程
fork
一个子进程,用于执行 RDB 持久化,完成后自动结束。 -
阻塞时间
阻塞只发生在 fork 阶段,一般时间很短
-
bgsave
流程其中
bgsave
操作可以由我们进入 Redis 客户端并且手动执行,也可以由 Redis 自动触发。 -
如何自动触发
redis.conf
中配置save m n
,即在 m 秒内有 n 次修改时,自动触发bgsave
生成rdb
文件- 主从复制时,从节点要从主节点进行全量复制时也会触发
bgsave
操作,生成当时的快照发送到从节点 - 执行
debug reload
命令重新加载 Redis 时也会触发bgsave
操作 - 默认情况下执行
shutdown
命令时,如果没有开启aof
持久化,那么也会触发bgsave
操作
-
接下来我们将实践触发 RDB 持久化机制
主动 save
触发 RDB
在
redis-cli
下执行save
命令
-
初始条件(这里的初始条件需要记住,接下来的实践都将基于这个初始化进行)⭐️
我们首先找一个空的 Redis 数据库,往里面写入 num1, num2
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> set num1 1 OK 127.0.0.1:6379> set num2 2 OK 127.0.0.1:6379> keys * 1) "num1" 2) "num2" 127.0.0.1:6379>
同时我们关注 Redis 的数据存储目录
root@VM-4-6-debian:/var/lib/docker/volumes/redis-data/_data# ls root@VM-4-6-debian:/var/lib/docker/volumes/redis-data/_data#
可以看到当前是没有任何数据的
基于「初始条件」我们执行 save
命令
127.0.0.1:6379> save
OK
这个时候我们检查数据目录,发现多出来了一个 dump.rdb
文件
root@VM-4-6-debian:/var/lib/docker/volumes/redis-data/_data# ls
dump.rdb
说明上面由于执行 save
命令主动触发了 RDB,将当前数据写入到了 rdb
文件
断开/重启 redis-cli
自动触发 RDB
默认情况下执行
shutdown
命令触发bgsave
基于「初始条件」,我们关闭客户端(执行 shutdown
命令)
127.0.0.1:6379> shutdown
not connected>
这个时候我们检查数据目录,发现多出来了一个 dump.rdb
文件
root@VM-4-6-debian:/var/lib/docker/volumes/redis-data/_data# ls
dump.rdb
说明上面由于执行 shutdown
命令自动触发了 bgsave
,将当前数据写入到了 rdb
文件
我们重启 Redis 服务,并且检查数据库中的 key
127.0.0.1:6379> keys *
1) "num1"
2) "num2"
127.0.0.1:6379>
发现数据都还在,说明数据被 Redis 通过 dump.rdb
恢复了。
我们直接重启 Redis 服务,相当于执行了
reload
,自动触发bgsave
基于「初始条件」,我们直接重启 redis 服务
然后可以发现存储 RDB 文件的目录下出现新的 dump.rdb
root@VM-4-6-debian:/var/lib/docker/volumes/redis-data/_data# ls
dump.rdb
以上的断开/重启触发 RDB 的机制,正是 Redis 默认情况下对数据的持久化保护手段,当然默认的 RDB 持久化保护还不仅仅如此,接下来我们将学习到 Redis 中默认的快照周期一样可以自动的持久化保护数据。
redis.conf 配置快照周期触发 RDB
在
redis.conf
中配置save m n
,确定在 m 秒内有 n 次修改,自动触发bgsave
我们首先在 redis.conf
中进行 RDB 的快照周期配置
-
什么是快照周期?
内存快照虽然可以通过手动执行
save
或bgsave
命令来进行,但生产环境下多数情况都会设置其周期性执行条件。于是我们设置
save m n
就是为内存快照设置一个执行的周期,也就是快照周期。 -
Redis 中默认的快照周期设置
我们打开
redis.conf
定位到SNAPSHOTTING
会看到以下内容:# 默认的设置为: save 900 1 save 300 10 save 60 10000
意思是说只要满足以下三个条件中的任意一个,就会自动触发
bgsave
:- 服务器在 900 秒之内,对数据库进行了至少 1 次修改
- 服务器在 300秒 之内,对数据库进行了至少 10 次修改
- 服务器在 60秒 之内,对数据库进行了至少 10000 次修改
-
其它 RDB 相关配置
## 设置成下面则是关闭 RDB 快照功能 # save "" # 文件名称 dbfilename dump.rdb # 文件保存路径 dir /data/ # 如果持久化出错,主进程是否停止写入 stop-writes-on-bgsave-error yes # 是否压缩 rdbcompression yes # 导入时是否检查 rdbchecksum yes
-
dbfilename
:RDB 文件在磁盘上的名称。
-
dir
:RDB 文件的存储路径。默认设置为 “./”,也就是 Redis 服务的主目录。
-
stop-writes-on-bgsave-error
:如果快照操作出现异常(例如操作系统用户权限不够、磁盘空间写满等等)时,Redis 就会禁止写操作。这个特性的主要目的是使运维人员在第一时间就发现 Redis 的运行错误,并进行解决。
-
rdbcompression
:该属性将在字符串类型的数据被快照到磁盘文件时,启用 LZF 压缩算法。Redis 官方的建议是请保持该选项设置为 yes。
-
rdbchecksum
:一个64位的 CRC 冗余校验编码会被放置在 RDB 文件的末尾,以便对整个 RDB 文件的完整性进行验证。这个功能大概会多损失10%左右的性能,但获得了更高的数据可靠性。所以如果您的 Redis 服务需要追求极致的性能,就可以将这个选项设置为 no。
-
基于「初始条件」,在我们上面学习基础上,我们修改自己的 redis.conf
文件:
# 文件名称
dbfilename dump.rdb
# 文件保存路径
dir /data
# 是否压缩
rdbcompression yes
# 30s内修改2次就bgsave
save 30 2
然后重启 redis
服务,并且直接删除掉由于重启生成的 dump.rdb
。
然后我们在 redis-cli
下写入两个数据:
127.0.0.1:6379> set num3 3
OK
127.0.0.1:6379> set num4 4
OK
等待30s,发现存储 rdb
文件的目录下出现了新的 dump.rdb
root@VM-4-6-debian:/var/lib/docker/volumes/redis-data/_data# ls
dump.rdb
说明上面配置的快照周期奏效了。
在学习完成 RDB 篇章后,我们回顾一下篇章开头讲的 RDB 的缺点有一个实时性不够,而这个实时性不够的缺点 Redis 提供了 AOF 来解决。
AOF
什么是 AOF?
将写命令添加到 AOF 文件(Append Only File)的末尾。
与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案。
默认情况下 Redis 没有开启 AOF(append only file)方式的持久化
AOF 是日志形式的持久化方案,因此什么时候写日志是我们需要注意的
什么是写后日志?和写前日志有什么区别?
Redis 就是写后日志,即执行命令,先写内存,再写日志。日志里面记录的是每一条命令。
而 MySQL 就是写前日志(WAL
机制),通过写前日志,和两阶段提交,保证数据和逻辑的一致性。
为什么使用写后日志?
首先 Redis 是一个追求高性能的数据库。
由于我们是先执行命令后再写日志,这样错误的命令就不会被记录在日志,因此 AOF 避免了对命令的语法检查,避免了额外的检查开销。
同时写后日志不会阻塞当前的写操作。
-
采用这个日志的问题
- 如果命令执行完成,而在写日志之前宕机了,那么会丢失数据。
- 主线程写磁盘压力大,导致写盘慢,阻塞后续操作
AOF 同步选项/写回策略
AOF 日志记录写命令的步骤
- 命令追加(append)
- 文件写入(write)
- 文件同步(sync)
命令追加步骤做了什么?
当 AOF 持久化功能打开了,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器的 aof_buf
缓冲区。
所以命令追加过程还是在「写内存」,而后面的文件写入和文件同步过程则是「写磁盘写日志」了。
文件写入和文件同步步骤需要专门制定策略以实现 AOF。Redis 提供了 3 种同步选项/写回策略
选项 | 同步频率 | 优点 | 缺点 |
---|---|---|---|
always | 每个写命令都同步 | 可靠性高,数据基本不丢失 | 每个写命令都要落盘,性能影响较大 |
everysec | 每秒同步一次 | 性能适中 | 宕机时丢失1s内的数据 |
no | 让操作系统来决定何时同步 | 性能好 | 宕机时丢失数据较多 |
至于选择什么样的写回策略?那就是要根据性能和可靠性进行取舍了!
配置并打开 AOF
Redis 中 AOF 相关配置
# appendonly参数开启AOF持久化
appendonly yes
# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"
# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
dir /data/
# 同步策略
# appendfsync always
appendfsync everysec
# appendfsync no
# aof重写期间是否同步
no-appendfsync-on-rewrite no
# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 加载aof出错如何处理
aof-load-truncated yes
# 文件重写策略
aof-rewrite-incremental-fsync yes
-
appendonly
:默认情况下 AOF 功能是关闭的(no),将该选项改为 yes 打开 Redis 的 AOF 功能。
-
appendfilename
:AOF 文件的名字。
-
appendfsync
:设置「真正执行」操作命令向 AOF 文件中同步的策略。
-
什么是真正执行?
为了保证操作系统中 I/O 队列的操作效率,应用程序提交的I/O操作请求一般是被放置在 linux
Page Cache
中的,然后再由 Linux 操作系统中的策略自行决定正在写到磁盘上的时机。而 Redis 中有一个fsync()
函数,可以将Page Cache
中待写的数据真正写入到物理设备上
-
-
no-appendfsync-on-rewrite
:always 和 everysec 的设置会使真正的 I/O 操作高频度的出现,甚至会出现长时间的卡顿情况,这个问题出现在操作系统层面上,所有靠工作在操作系统之上的 Redis 是没法解决的。
为了尽量缓解这个情况,Redis 提供了这个设置项,保证在完成
fsync
函数调用时,不会将这段时间内发生的命令操作放入操作系统的Page Cache
(这段时间 Redis 还在接受客户端的各种写操作命令)。 -
auto-aof-rewrite-percentage
:在生产环境下,技术人员不可能随时随地使用
BGREWRITEAOF
命令去重写 AOF 文件。所以更多时候我们需要依靠 Redis 中对 AOF 文件的自动重写策略。该参数表示如果当前 AOF 文件的大小超过了上次重写后 AOF 文件的百分之多少后,就再次开始重写 AOF 文件。
例如该参数值的默认设置值为 100,意思就是如果 AOF 文件的大小超过上次 AOF 文件重写后的1倍,就启动重写操作。
-
auto-aof-rewrite-min-size
:表示启动 AOF 文件重写操作的 AOF 文件最小大小。如果 AOF 文件大小低于这个值,则不会触发重写操作。
在了解上面的配置选项后我们来自己实践一下
首先还是配置我们自己的 redis.conf
文件,我们打开 AOF:
# aof 文件存储位置
dir /data
# appendonly参数开启AOF持久化
appendonly yes
# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"
# 同步策略
appendfsync everysec
然后我们重启 Redis 服务,并且检查文件存储位置,发现多出来了新的 appendonly.aof
文件
root@VM-4-6-debian:/var/lib/docker/volumes/redis-data/_data# ls
appendonly.aof dump.rdb
至于上面为什么还有 dump.rdb
那是因为此时 RDB 还未关闭,RDB 和 AOF 同时打开了。于是我们重新修改一下配置:
# aof 文件存储位置
dir /data
# 关闭 rdb
save ""
# appendonly参数开启AOF持久化
appendonly yes
# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"
# 同步策略
appendfsync everysec
重启 Redis 并检查,发现现在只有 appendonly.aof
了:
root@VM-4-6-debian:/var/lib/docker/volumes/redis-data/_data# ls
appendonly.aof
root@VM-4-6-debian:/var/lib/docker/volumes/redis-data/_data# cat appendonly.aof
root@VM-4-6-debian:/var/lib/docker/volumes/redis-data/_data#
该 aof
文件当前为空,然后我们执行命令,往 Redis 中写入数据:
127.0.0.1:6379> set num1 1
OK
127.0.0.1:6379> set num2 2
OK
再次检查 appendonly.aof
文件:
root@VM-4-6-debian:/var/lib/docker/volumes/redis-data/_data# cat appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$4
num1
$1
1
*3
$3
set
$4
num2
$1
2
发现我们执行的写命令都按照格式写入到 aof
文件中了。
接着我们关闭 Redis 连接,来测试一下 AOF 持久化的数据恢复:
127.0.0.1:6379> shutdown
not connected>
然后重启 Redis 服务,并且检查数据:
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> keys *
1) "num2"
2) "num1"
数据成功的通过 AOF 恢复了!
持久化中恢复数据
持久化做完了,也产生了持久化文件了。那么我们如何从持久化中恢复数据?如果又有 RDB 文件又有 AOF 文件,应该如何加载?
想从这些文件中恢复数据实际上只需要重启 Redis 服务就行了。(这也是我们上面实践一直在做的恢复操作)
然后 Redis 会经过下面的流程来执行恢复:
所以说,重启 Redis 的时候会进行判断,如果开启了 AOF,则优先加载 aof
文件。加载成功则 Redis 重启成功,加载失败,则会打印日志表示启动失败,那么需要去修复 aof
文件后重新启动。
如果 aof
文件不存在,那么 Redis 去加载 rdb
文件,如果 rdb
文件不存在,则 Redis 直接重启成功。如果 rdb
文件存在,则去加载 rdb
文件,如加载失败则打印日志提示启动失败,如加载成功,那么 Redis 重启成功,且使用 rdb
文件恢复数据;
其中通过以上过程,我们要知道 Redis 优先加载 aof
文件的原因是因为 AOF 保存的数据更加完整。
小结
本篇文章我们从 Redis 数据持久化的需求出发,详细介绍了 RDB 和 AOF 持久化机制,并且从多角度实践了这两种持久化机制,分析了其中的原理。最后我们分析了 Redis 从持久化文件中加载数据的机制。通过本篇文章应该可以对于 Redis 持久化机制形成一个较为全面的了解。
本篇参考:
转载自:https://juejin.cn/post/7205094404415946810