Redis 数据持久化
1. 什么是持久化
1.1 概念
Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据不会因为故障而丢失,这种机制就是 Redis 的持久化机制
Redis持久化技术就是将内存中的数据库数据保存到磁盘中
1.2 两种持久化的方式
RDB(Redis DataBase)
AOF(Append Of File)
官网文档地址:https://redis.io/docs/manual/persistence/
2. RDB
2.1 作用
在指定的时间间隔内将内存中的所有数据集快照写入磁盘,也就是行话讲的 Snapshot (快照),它执行的是全量快照,它恢复时是将快照文件直接读到内存里
这就类似于照片,当你拍照时,一张照片就能把拍照那一瞬间的形象完全记下来
2.2 原理
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束后,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
RDB的缺点是最后一次持久化后的数据可能丢失。
2.3 Fork
-
Redis 使用操作系统的写时复制技术 COW(Copy On Write) 机制来实现快照持久化(在执行快照的同时,正常处理写操作)
-
Fork(多进程) 的作用是:Redis 在持久化时会调用 glibc 的函数 fork 产生一个子进程(所有数据,包括:变量、环境变量、程序计数器等数值都和原进程一致),快照持久化完全交给子进程来处理,父进程继续处理客户端请求
-
主进程需要修改数据时的处理
-
bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。
-
bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。
-
此时,如果主线程对这些数据也都是读操作(例如图中的键值对K1),那么,主线程和 bgsave 子进程相互不影响。
-
但是,如果主线程要修改一块数据(例如图中的键值对 K3),那么,这块数据就会被复制一份,生成该数据的副本。
-
然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
-
2.4 快照运行过程
-
当 Redis 需要保存 dump.rdb 文件时, 服务器执行bgsave命令后则执行以下操作:
-
Redis 调用
fork()
,产生一个子进程,此时同时拥有父进程和子进程。 -
父进程继续处理 client 请求,子进程负责将内存内容写入到临时文件。由于 os 的写时复制机制,父子进程会共享相同的物理页面,当父进程处理写请求时, os 会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数据是 fork 时刻整个数据库的一个快照。
-
当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
-
-
如图:
2.5 相关配置
配置文件名
快照持久化是Redis中默认开启的持久化方案
根据redis.conf中的配置,快照将被写入dbfilename指定的文件中(默认是dump.rdb文件)。
# The filename where to dump the DB
dbfilename dump.rdb
-
配置文件路径:根据redis.conf中的配置,快照将保存在dir选项指定的路径上,我们可以修改为指定目录
# Note that you must specify a directory here, not a file name.
dir ./
注意:由于此路径是一个相对路径,它会根据执行命令所在目录来生成dump.rdb文件,因此建议改为一个固定位置,如:/usr/local/redis/data
- save
作用:配置备份规则
格式:save 秒钟 写操作次数
save 900 1 # 在900s内如果有1条数据被写入,则产生一次快照。
save 300 10 # 在300s内如果有10条数据被写入,则产生一次快照
save 60 10000 # 在60s内如果有10000条数据被写入,则产生一次快照
stop-writes-on-bgsave-error yes # 如果为yes则表示,当备份进程出错的时候,主进程就停止进行接受新的写入操作,这样是为了保护持久化的数据一致性的问题。
使用演示:
把持久化的策略修改为
save 30 5
其表示 30 秒内写入 5 条数据就产生一次快照,也就是生成 rdb 文件
修改好的保存并重启 redis 服务,然后打开客户端,执行如下命令:
[root@server ~]# vim /etc/redis/redis.conf
[root@server ~]# systemctl restart redis
[root@server ~]# redis-cli -p 6379
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK
127.0.0.1:6379> set k4 v4
OK
127.0.0.1:6379> set k5 v5
OK
127.0.0.1:6379> set k6 v6
OK
127.0.0.1:6379> exit
[root@server ~]# ls /
-
然后查看 dump.rdb 文件的大小,就可以发现文件快照已经产生了
问题:是否这 6 条数据都持久化了?
答:只持久化了前 5 条数据,最后 1 条数据没有持久化。
注意:Redis 中持久化命令有两种:
save:手动保存,只管保存不管其它,所有操作全部阻塞。不建议使用。
bgsave:自动保存,Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。
-
stop-writes-on-bgsave-error:当 Redis 无法写入磁盘的话,直接关掉 Redis 的写操作,推荐配置为 yes。
-
rdbcompression
-
对于存储到磁盘中的快照,可以设置是否进行压缩存储。
-
如果是的话,Redis 会采用 LZF 算法进行压缩。
-
如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。推荐配置为 yes。
-
-
rdbchecksum
-
在存储快照后,还可以让 Redis 使用 CRC64 算法来进行数据校验,检查数据完整性。
-
但是这样做会增加大约 10% 的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。推荐配置为 yes。
-
2.6 备份
演示如下:首先执行如下命令来清空数据
[root@server ~]# redis-cli -p 6379127.0.0.1:6379> flushdb
-
然后再重新添加如下数据:
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK
127.0.0.1:6379> set k4 v4
OK
127.0.0.1:6379> set k5 v5
OK
127.0.0.1:6379> set k6 v6
OK
127.0.0.1:6379> exit
- 接着执行如下命令来备份 dump.rdb
[root@server ~]# cp /dump.rdb /dump.rdb.back
- 最后把 Redis 服务关闭
[root@server ~]# redis-cli shutdown
- 然后把 dump.rdb 文件删除
[root@server ~]# rm -f /dump.rdb
2.7 恢复
-
首先执行如下命令来把 dump.rdb.back 文件修改为 dump.rdb。
[root@server ~]# mv /dump.rdb.back /dump.rdb
- 然后重启 Redis 服务。
[root@server ~]# systemctl start redis.service
-
然后连接客户端后再执行如下命令来查看:
[root@server ~]# redis-cli -p 6379
127.0.0.1:6379> keys * # 多个s
1) "k1"
2) "k2"
3) "k3"
4) "k4"
5) "k5"
2.8 优势
适合大规模数据恢复
对数据完整性和一致性要求不高更适合使用
节省磁盘空间
基于二进制存储的,恢复速度快
2.9 劣势
Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。
3. AOF
3.1 作用
以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据。
简单说,Redis 重启时会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
在Redis的默认配置中AOF(Append Only File)持久化机制是没有开启的,要想使用AOF持久化需要先开启此功能。
AOF持久化会将被执行的写命令写到AOF文件末尾,以此来记录数据发生的变化,因此只要Redis从头到尾执行一次AOF文件所包含的所有写命令,就可以恢复AOF文件的记录的数据集。
3.1.1 AOF 日志是如何实现的
-
日志,比较熟悉的是数据库的写前日志(Write Ahead Log, WAL),也就是说,在实际写数据前,先把修改的数据记到日志文件中,以便故障时进行恢复(DBA 们常说的“日志先行”)。
-
不过,AOF 日志正好相反,它是写后日志,“写后”的意思是 Redis 是先执行命令,把数据写入内存,然后才记录日志,如下图所示:
-
注意:
-
日志先行的方式,如果宕机后,还可以通过之前保存的日志恢复到之前的数据状态。
-
若使用 AOF 后写日志的方式,如果宕机后,不就会把写入到内存的数据丢失吗? 那 AOF 为什么要先执行命令再记日志呢?要回答这个问题,我们要先知道 AOF 里记录了什么内容。
-
-
AOF日志记录原理
-
传统数据库的日志,例如 redo log(重做日志),记录的是修改后的数据,而 AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的。
-
以 Redis 收到 “set k1 v1” 命令后记录的日志为例,看看 AOF 日志的内容。
-
其中:
-
“ *2” 表示当前命令有两个部分,每部分都是由
“$+数字”
开头,后面紧跟着具体的命令、键或值。这里,“数字”表示这部分中的命令、键或值一共有多少字节。 -
例如,*2 表示有两个部分,6表示6个字节,也就是下边的“SELECT”命令,1 表示 1 个字节,也就是下边的 “0” 命令,合起来就是 SELECT 0,选择 0 库。下边的指令同理,就很好理解了 SET K1 V1。
-
-
-
结论:
-
为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。
-
所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。
-
而写后日志这种方式,就是先让系统执行命令,只有命令能执行成功,才会被记录到日志中,否则,系统就会直接向客户端报错。
-
所以,Redis 使用写后日志这一方式的一大好处是,可以避免出现记录错误命令的情况。除此之外,AOF 还有一个好处:它是在命令执行后才记录日志,所以不会阻塞当前的写操作
-
3.2 原理
客户端的请求写命令会被 append 追加到 AOF 缓冲区内。
AOF 缓冲区根据 AOF 持久化策略 [always,everysec,no] 将操作sync同步到磁盘的 AOF 文件中。
AOF 文件大小超过重写策略或手动重写时,会对 AOF 文件 rewrite 重写,压缩 AOF 文件容量。
Redis 服务重启时,会重新 load 加载 AOF 文件中的写操作达到数据恢复的目的。
3.3 使用AOF
开启AOF
[root@server ~]# vim /etc/redis/redis.conf
appendonly yes # no改yes,开启AOF持久化
# 通过appendfilename指定日志文件名字(默认为appendonly.aof)
# 通过appendfsync指定日志记录频率
配置选项
选项 | 同步频率 |
---|---|
always | 每个redis写命令都要同步写入硬盘,严重降低redis速度 |
everysec | 每秒执行一次同步显式的将多个写命令同步到磁盘 |
no | 由操作系统决定何时同步 |
三个选项详细说明如下:
always选项,每个redis写命令都会被写入AOF文件中,好处是当发生系统崩溃时数据丢失减至最少,缺点是这种策略会产生大量的I/O操作,会严重降低服务器的性能。
everysec选项,以每秒一次的频率对AOF文件进行同步,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器几乎没有任何影响。
- no选项,完全由操作系统决定什么时候同步AOF日志文件,这个选项不能给服务器性能带来多大的提升,反而会增加系统崩溃时数据丢失的数量。
使用演示
# 停止 Redis 服务,然后再重新开启 Redis 服务后,就可以在/目录下会生成多个以appendonly开头的文
[root@server ~]# redis-cli shutdown
[root@server ~]# systemctl start redis
[root@server ~]# ls /appendonlydir/
appendonly.aof.1.base.rdb # 基本文件,是一个快照,表示文件创建时数据集的完整状态
appendonly.aof.1.incr.aof # 增量文件, 包含应用于前一个文件之后的数据集的额外命令
appendonly.aof.manifest # 清单文件,用于跟踪文件及其创建和应用的顺序
[root@server ~]# redis-cli -p 6379
127.0.0.1:6379> set k11 v11
OK
127.0.0.1:6379> set k12 v12
OK
127.0.0.1:6379> set k13 v13
OK
127.0.0.1:6379> set k14 v14
OK
127.0.0.1:6379> set k15 v15
OK
127.0.0.1:6379> exit
# # 查看增量内容
[root@server ~]# cat /appendonlydir/appendonly.aof.1.incr.aof
3.4 Rewrite
-
Rewrite是什么
-
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制
-
当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩, 只保留可以恢复数据的最小指令集。
-
可以使用命令bgrewriteaof实现,该操作相当于“瘦身”,
-
在重写的时候,是根据这个键值对当前的最新状态,为它生成对应的写入命令。这样一来,一个键值对在重写日志中只用一条命令就行了,而且,在日志恢复时,只用执行这条命令,就可以直接完成这个键值对的写入了
-
-
重写(减肥)原理
-
AOF 文件持续增长而过大时,会 fork 出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,转换成一条条的操作指令,再序列化到一个新的 AOF 文件中。
-
参数no-appendfsync-on-rewrite=yes ,表示不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。 这样做可以降低数据安全性,提高性能。
-
如果参数no-appendfsync-on-rewrite=no, 则还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。这样做可以数据安全,但是性能降低。- - -
-
-
触发机制,何时重写
-
Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
-
重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。
-
参数auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)
-
参数auto-aof-rewrite-min-size:设置重写的基准值,最小文件64MB。达到这个值开始重写。
-
3.5 重写流程
Redis 执行
fork()
,现在同时拥有父进程和子进程。子进程开始将新 AOF 文件的内容写入到临时文件。
对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾:这样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。
当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。
现在 Redis 使用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾,如图:
3.6 优势
备份机制更稳健,丢失数据概率更低。
可读的日志文本,通过操作AOF稳健,可以处理误操作。
3.7 劣势
比起RDB占用更多的磁盘空间。
恢复备份速度要慢。
每次读写都同步的话,有一定的性能压力。
存在个别Bug,造成恢复不能。
4. 如何选择
-
那么,在开发中是选择 RDB 还是选择 AOF 来持久化呢?官网建议如下:
-
官方推荐使用 RDB 与 AOF 混合式持久化。
-
若对数据安全性要求不高,则推荐使用纯 RDB 持久化方式。
-
不推荐使用纯 AOF 持久化方式。
-
若 Redis 仅用于缓存,则无需使用任何持久化技术
-