2.1. 概述
Redis的确是将数据存储在内存的,但是也会有相关的持久化机制将内存持久化备份到磁盘,以便于重启时数据能够重新恢复到内存中,避免数据丢失的风险。
而Redis持久化机制由三种,在4.X版本之前Redis只支持AOF以及RDB两种形式持久化,但是因为AOF与RDB都存在各自的缺陷,所以在4.x版本之后Redis还提供一种新的持久化机制:混合型持久化
2.2. RDB
RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件称为RDB文件,默认是保存在当前运行目录。
2.2.1. 执行时机
- save:主线程,用户主动或者关机
- bgsave:子线程,触发机制
save
save命令会导致主进程执行RDB,这个过程中其它所有命令都会被阻塞。只有在数据迁移时可能用到。在生产环境切记不可使用。
bgsave
bgsave会使主进程fork 出一个子进程来异步进行 RDB 持久化。 需要注意的是不阻塞主进程指的是子进程中的操作不会阻塞主进程,但是 fork 这个动作本身是会阻塞主进程的,因为 fork 子进程的操作就是在主进程中完成的。
触发机制可以在redis.conf文件中找到,格式如下:
//900秒内,如果至少有1个key被修改,则执行bgsave,其他同理 save 900 1 save 300 10 save 60 10000 //表示禁用RDB save "" //是否压缩 ,建议不开启,压缩也会消耗cpu,磁盘的话不值钱 rdbcompression yes //导入时是否检查 rdbchecksum yes //RDB文件名称 dbfilename dump.rdb //文件保存的路径目录 dir ./ //当备份进程出错时,主进程就停止接受新的写入操作 stop-writes-on-bgsave-error yes
2.2.2. 特点
- 优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 Redis 的高性能;而且RDB文件存储的是压缩的二进制文件,适用于备份、全量复制,可用于灾难备份,同时RDB文件的加载速度远超于AOF文件。
- 缺点:RDB是间隔一段时间进行持久化,如果持久化之间的时间内发生故障,会出现数据丢失。所以这种方式更适合数据要求不严谨的时候,因为RDB无法做到实时持久化,而且每次都要创建子进程,频繁创建成本过高;备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(需要的内存是原本的两倍);还有一点,RDB文件保存的二进制文件存在新老版本不兼容的问题。
2.2.3. 原理
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。
fork采用的是copy-on-write技术:
- 当主进程执行读操作时,访问共享内存;
- 当主进程执行写操作时,则会拷贝一份数据,执行写操作。
主进程与子进程共享的是?
主进程和子进程共享页表,共享的是映射关系
为什么主进程和子进程是不同进程,不会出现端口冲突?
主进程和子进程共享端口
为什么要使用copy-on-write?
子进程在执行 RDB 时,若父进程修改了内存数据,则会导致子进程读取的内存数据一起修改了,这样不合理。可是为什么不合理呢?最后保存在 RDB 中的数据有一部分是最新的,应该更不会有问题才对呀?
这跟java的并发问题是一样的,子进程读的值跟期望值不一致,也属于脏读;另外混合持久化会结合RDB和AOF,在最终数据合并会出现数据不一致。所以务必保证子进程的内存数据和 fork 时内存数据一致。
举例分析:
子进程开始进行 RDB,此时没有 "xxx" 这个 key 主进程收到命令 incr("xxx"), 此时 xxx 对应的值是 1(同时AOF命令保存) 子进程保存 RDB 文件时也保存了 xxx ,文件内存在key=xxx, value=1 最终数据合并,最终DB中出现key=xxx, value=2
2.3. AOF
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
在发生故障后可以做到不丢数据或丢失较少的数据,是一种增量化持久方式,也可以看做对RDB这种全量化持久方式的一种补充。
2.3.1. 执行时机
一般记录日志可能都是在正式操作之前先记录日志,不过 AOF 是在执行命令之后才记录的日志,这样做有优点也有缺点。
- 优点是记录日志时不用检查命令是否正确,因为如果命令可以正确执行,那命令一定是正确的。
- 缺点是会出现有可能命令执行成功,但是日志记录失败。AOF 日志虽然没有阻塞当前命令,但是可能阻塞后续命令,因为 AOF 日志也是在主线程写入 AOF缓冲区的 这两个缺点都和 AOF 日志刷盘时机有关
AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF:
# 是否开启AOF功能,默认是no appendonly yes # AOF文件的名称 appendfilename "appendonly.aof" # 表示每执行一次写命令,立即记录到AOF文件 appendfsync always # 追加同步策略 # 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案 appendfsync everysec # 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘 appendfsync no
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 //选择redis数据库,默认存在16个 select 0 //set 值 set num1 1 set num2 2
AOF策略
配置项 |
刷盘时机 |
优点 |
缺点 |
Always |
同步刷盘 |
可靠性高,几乎不丢数据 |
性能影响大 |
everysec |
每秒刷盘 |
性能适中 |
最多丢失1秒数据 |
no |
操作系统控制 |
性能最好 |
可靠性较差,可能丢失大量数据 |
2.3.2. 特点
- 优点:根据不同的fsync策略可以保证数据丢失风险降到最低,数据能够保证是最新的,fsync是后台线程在处理,所以对于处理客户端请求的线程并不影响。
- 缺点:文件体积由于保存的是所有命令会比RDB大上很多,而且数据恢复时也需要重新执行指令,在重启时恢复数据的时间往往会慢很多。虽然fsync并不是共用处理客户端请求线程的资源来处理的,但是这两个线程还是在共享同一台机器的资源,所以在高并发场景下也会一定受到影响。
2.3.3. 重写机制
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。
Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置:
# AOF文件比上次文件 增长超过多少百分比则触发重写 auto-aof-rewrite-percentage 100 # AOF文件体积最小多大以上才触发重写 auto-aof-rewrite-min-size 64mb
2.3.4. 重写流程
- 主进程 fork 子进程,此时子进程拥有 Redis 内存快照
- 子进程扫描内存中的全部键值对,生成 Rewrite 临时文件
- 在此期间主进程将 AOF 日志同时写入 AOF 缓冲区和 Rewrite 缓冲区,并且 AOF 缓冲区依然会正常刷盘
- 子进程完成 Rewrite 操作后,主进程会将 Rewrite 缓冲区数据写入临时文件,然后将临时文件重命名,替换原来的 AOF 文件
- 后续的 AOF 日志写入到这个 Rewrite 之后的 AOF 文件中
2.4. 两者对比
项目 |
RDB |
AOF |
数据完整性 |
不完整,两次备份之间会丢失 |
相对完整,取决于刷盘策略 |
文件大小 |
会有压缩,文件体积小 |
记录命令,文件体积很大 |
宕机恢复速度 |
很快 |
较慢 |
数据恢复优先级 |
低,因为数据完整性不如AOF |
高,因为数据完整性更高 |
系统资源占用 |
高,大量CPU和内存消耗 |
低,主要是磁盘I/O资源,但AOF重写时会占用大量CPU和内存资源 |
使用场景 |
可以容忍数分钟的数据丢失,追求更快的启动速度 |
对数据安全性要求较高常见 |