Redis 持久化:RDB & AOF
Redis 是基于内存的数据库,存在数据丢失的风险。
因此,Redis 提供了三种持久化机制:RDB、AOF 以及 混合持久化。
一、RDB:全量快照
RDB, Redis DataBase,快照。RDB是一个二进制文件。
禁用RDB(默认开启)
arduino
# 如果是save "" 则表示禁用RDB
save ""
RDB触发时机
1、自动触发
bash
# 代表60秒内,如果至少有10000个key被修改,则执行bgsave命令
save 60 10000
# 可以设置多个 比如同时存在 save 5 100 满足任意一个条件都会触发
另外,主从同步时也会触发。
当从节点全量复制时,主节点会执行 bgsave
命令,将 RDB 文件发送给从节点。
Redis正常停机(关机)时也会执行一次RDB。但是突然宕机是来不及执行RDB的。
2、主动触发:save
两个命令:save
和 bgsave
。save会阻塞Redis,bgsave几乎不会。
bgsave实现原理
开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。
几乎不阻塞
在 fork() 子进程时仍然有短暂的阻塞,不是完全不阻塞。
bgsave过程中的写操作:写时复制
Redis采用copy-on-write技术:
当主进程执行写操作时,则会拷贝一份数据,执行写操作。这样就不会影响bgsave子进程的快照。
写时复制的弊端
写时复制在极端情况下,会占用两倍于Redis内存的物理内存,对于物理内存是一个挑战。
其次,写时复制是会消耗性能的,尤其是开启了「内存大页」的情况。
「内存大页」是指Linux分配一个2MB的内存页,这使得在只修改很少的数据时却不得不复制2MB的大页,因此需要关闭「内存大页机制」
typescript
echo never > /sys/kernel/mm/transparent_hugepage/enabled
RDB的缺点
- RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险(及两者中间宕机了,那么中间的数据都没有了。
- fork子进程、压缩、写出RDB文件都比较耗时。如果数据集很大,fork带来的阻塞可能会长达一秒。
二、AOF:增量操作
AOF, Append Only File,文件追加
AOF会把 Redis 每个键值对操作都记录到文件(appendonly.aof)中。
开启AOF(默认关闭)
bash
appendonly yes # 是否开启AOF功能,默认是no
AOF触发时机
Redis 是先执行命令,把数据写入内存,然后才记录日志
1、自动触发
这里涉及到一个AOF内存缓冲区,在 appendfsync everysec
策略下是先写入缓冲区,再一次性写入AOF文件
bash
# 写命令执行完先放入AOF缓冲区(内存操作),然后表示每隔1秒将缓冲区数据写到AOF文件(磁盘IO),是默认方案
appendfsync everysec
# 还有no 和 always策略
# no交给OS来管理 , Linux默认 30s 写入一次数据至磁盘
# always 每次都直接写入文件
2、主动触发:bgrewriteaof
执行bgrewriteaof命令
写入AOF文件是谁负责
策略不同,负责人不同
- 如果是
appendfsync always
,是Redis主进程 - 如果是
appendfsync everysec
,是一个异步任务 - 如果是
appendfsync no
,是交给操作系统管
everysec 和 always 会发起 fsync 系统调用
no会发起 write 系统调用
另外,bgrewriteaof主动触发,是fork一个子进程进行。
AOF文件膨胀问题
AOF是对操作的追加写,只会变大不会变小,因此会膨胀,带来如下问题:
- Redis重启要遍历整个AOF文件,无效操作会降低启动速度
- 文件无限膨胀,影响写入速度,并且会超过OS对单个文件的大小限制
要解决这个问题,我们需要重写AOF文件使得它变小。
AOF文件重写:解决AOF文件膨胀问题
AOF文件重写的本质
本质就是读取整个Redis数据库,读到一个键值对,然后以set k v的形式作为命令写入AOF文件
是不是和RDB很像,但是比RDB占据的空间更大
子进程重写
AOF文件很大,重写操作很重,不能占用主进程导致阻塞,因此bgrewriteaof 时,主线程 fork 出后台的 bgrewriteaof 子进程来进行重写。
AOF文件重写触发时机
1、自动重写
arduino
# AOF文件比上次文件 增长超过多少百分比则触发重写,默认为 100
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写,默认为 64mb
auto-aof-rewrite-min-size 64mb
- AOF文件大小翻倍,重写
- AOF文件大小至少
auto-aof-rewrite-min-size
才会触发重写 - 以上两个条件需要同时满足才会自动重写
因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义,因此需要重写AOF文件,但会占用大量CPU和内存资源
2、手动重写:bgrewriteaof
bgrewriteaof不仅会持久化AOF,也会重写AOF。
重写过程中的写操作
重写过程中,有两个日志,一个旧的大AOF日志,一个新的正在重写的AOF日志。
此时,写操作会同时写入两个日志的AOF缓冲区。
AOF重写的风险:阻塞fsync
AOF 重写会对磁盘进行大量 IO 操作,同时,fsync 又需要等到数据写到磁盘后才能返回,所以,当 AOF 重写的压力比较大时,就会导致 fsync 被阻塞。
fsync 阻塞还会进一步导致主线程阻塞。
当主线程让子线程 fsync 时,发现上一次 fsync 还没有执行完,主线程也会阻塞。
解决方案
对于always/everysec的刷盘机制,只有一种方案:直接不fsync
yaml
# 表示在 AOF 重写时,不进行 fsync 操作 默认 no
no-appendfsync-on-rewrite yes
此时,Redis把写命令写到内存后,不调用后台线程进行 fsync 操作,直接返回。
缺点是:此时实例发生宕机,就会导致数据丢失。
AOF文件异常处理
手动修复
使用 AOF 修复工具,检测出现的问题,在命令行中输入 redis-check-aof
命令,它会跳转到出现问题的命令行,这个时候可以尝试手动修复此文件;
自动修复
如果无法手动修复,我们可以使用 redis-check-aof --fix
自动修复 AOF 异常文件,不过执行此命令,可能会导致异常部分至文件末尾的数据全部被丢弃。
AOF的缺点
AOF 文件通常更大,负载比较高的情况下,RDB 比 AOF 性能更好;
注意:同时开启RDB 和 AOF,Redis 启动时只会加载 AOF 文件
三、混合持久化
混合下的AOF文件重写
在开启混合持久化的情况下,AOF 重写时会把 Redis 的持久化数据,以 RDB 的格式写入到 AOF 文件的开头,(bgsave过程中的操作以及)之后操作的数据再以 AOF 的格式,追加在文件的末尾。
开启混合持久化
bash
aof-use-rdb-preamble yes
# 还需要开启 AOF 和 RDB
混合持久化的Redis启动流程
一般情况下,会先加载AOF文件中的RDB部分,再加载AOF部分
混合持久化的优点
兼具AOF 与 RDB的优点
混合 vs AOF
因为涉及RDB,比AOF数据恢复速度更快
文件大小比纯AOF文件更小
混合 vs RDB
丢失数据的风险比RDB更低;
Redis持久化实战
一般需要持久化的情况,就用混合持久化即可。
主从架构下,从节点不需要持久化。
《Redis 核心技术与实战》给出的建议是:
- 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择;
- 如果允许分钟级别的数据丢失,可以只使用 RDB;
- 如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。
参考文档
《Redis 核心技术与实战》
《Redis 核心原理与实战》