1 前言
Redis支持RDB
和AOF
两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复。理解掌握持久化机制对于Redis运维非常重要。
2 AOF
AOF(append only file)持久化:以独立日志
的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。
AOF的主要作用是解决了数据持久化的实时性
,目前已经是Redis持久化的主流方式。
注意只会记录
写操作
命令,读操作
命令是不会被记录的,因为没意义。
2.1 配置
默认情况,AOF是关闭的,若要开启,需要配置参数,如下所示:
appendonly yes //开启AOF持久化,默认为no,不开启AOF
appendfilename appendonly.aof //配置AOF持久化文件名,默认为appendonly.aof
文件保存路径也可以自定义,默认在Redis服务的主目录./
。
# 文件保存路径
dir /home/work/app/redis/data/
2.2 流程
Redis是“写后”
日志,Redis先执行命令,把数据写入内存,然后才记录日志。日志里记录的是Redis收到的每一条命令,这些命令是以文本
形式保存。
这种先写数据至内存,再写日志至磁盘
的写后日志
的方式优点有:
- 避免额外的检查开销:若先写日志,Redis并不会对这些命令进行语法检查,保存下来的可能是错误的命令,那在恢复过程中就会出问题
- 不会阻塞当前写操作命令的执行
缺点:
- 如果命令执行完成,写日志之前宕机了,会丢失数据。
- 主线程写磁盘压力大,导致写盘慢,阻塞后续操作。
因为将命令写入到日志的这个操作也是在
主进程
完成的(执行命令也是在主进程),也就是说这两个操作是同步的
。
AOF方式涉及的流程包括:命令追加、文件写入与同步、文件重写、重启加载、文件校验。
2.2.1 命令追加
当AOF持久化功能打开了,服务器在执行完一个写命令之后,会以文本协议格式
将被执行的写命令追加
到服务器的server.aof_buf
缓冲区。
文本协议格式源于Redis制定的RESP
(REdis Serialization Protocol,Redis序列化协议)[^1],其实现了客户端与服务端的正常交互,这种协议简单高效,既能够被机器解析,又容易被人类识别。
例如客户端发送一条set hello world
命令给服务端,按照RESP
的标准,客户端需要将其封装为如下格式(每行用\r\n
分隔):
*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n
实际显示效果可以如下:
*3
$3
SET
$5
hello
$5
world
各行解释可按系列方式对号入座:
*<参数数量> CRLF
$<参数1的字节数量> CRLF
<参数1> CRLF
...
$<参数N的字节数量> CRLF
<参数N> CRLF
*3
表示当前命令有三个部分
;$+数字
:$
分隔每个部分、数字
表示每个部分的字节数;- 紧跟着的为具体的
命令
、键
或值
,比如SET(命令)、hello(键)、world(值)。
1)AOF为什么直接采用文本协议格式?
可能的理由如下:
- 文本协议具有很好的兼容性。
- 开启AOF后,所有写入命令都包含追加操作,直接采用协议格式,避免了二次处理开销。
- 文本协议具有可读性,方便直接修改和处理。
2)AOF为什么把命令追加到aof_buf中?
Redis使用单线程响应命令,如果每次写AOF文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负载。先写入缓冲区
aof_buf
中,还有另一个好处,Redis可以提供多种缓冲区同步硬盘的策略,在性能和安全性方面做出平衡。
2.2.2 文件写入与同步
Redis提供了多种AOF缓冲区同步文件策略,由参数appendfsync
控制,不同值的含义如下所示:
Always
,同步写回:每个写命令执行完,立马同步
地将日志写回磁盘;Everysec
,每秒写回,默认值
:每个写命令执行完,只是先把日志写到AOF文件的内核缓冲区
,每隔一秒
把缓冲区中的内容写入磁盘;No
,操作系统控制的写回:每个写命令执行完,只是先把日志写到AOF文件的内核缓冲区
,由操作系统决定何时将缓冲区内容写回磁盘。
这 3 种写回策略都无法能完美解决主进程阻塞
和减少数据丢失
的问题,因为两个问题是对立的,偏向于一边的话,就会要牺牲另外一边,原因如下:
Always
策略的话,可以最大程度保证数据不丢失,但是由于它每执行一条写操作命令就同步将 AOF 内容写回硬盘,所以是不可避免会影响主进程的性能;No
策略的话,是交由操作系统来决定何时将 AOF 日志内容写回硬盘,相比于Always
策略性能较好,但是操作系统写回硬盘的时机是不可预知的,如果 AOF 日志内容没有写回硬盘,一旦服务器宕机,就会丢失不定数量的数据。Everysec
策略的话,是折中的一种方式,避免了Always
策略的性能开销,也比No
策略更能避免数据丢失,当然如果上一秒的写操作命令日志没有写回到硬盘,发生了宕机,这一秒内的数据自然也会丢失。
大家根据自己的业务场景进行选择:
- 如果要
高性能
,就选择No
策略; - 如果要
高可靠
,就选择Always
策略; - 如果允许数据丢失一点,但又想性能高,就选择
Everysec
策略。
关于AOF的同步策略是涉及到操作系统的 write
函数和 fsync
函数的。
为了提高文件的写入效率, 在现代操作系统中, 当用户调用
write
函数, 将一些数据写入到文件的时候, 操作系统通常会将写入数据暂时保存在一个内存缓冲区里面, 等到缓冲区的空间被填满、或者超过了指定的时限之后, 才真正地将缓冲区中的数据写入到磁盘里面。这种做法虽然提高了效率, 但也为写入数据带来了安全问题, 因为如果计算机发生停机, 那么保存在内存缓冲区里面的写入数据将会丢失。
为此,系统提供了
fsync
和fdatasync
两个同步函数, 它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面, 从而确保写入数据的安全性。
AOF写入的过程可总结为如下图:
2.2.3 文件重写[^2][^3]
随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入AOF重写机制
压缩文件体积。
AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。
这样做的好处主要有两方面:
- 节省存储空间
- 更快地加载
为啥AOF文件大小能够压缩、变小。原因如下:
- 超时的数据不必再写入文件
- 旧的AOF文件可能还有无效的命令,如del key1、hdel key2、srem keys、set a111、set a222等。重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。
- 多条命令可以合并。如
lpush list a
、lpush list b
、lpush list c
可以转化为:lpush list a b c
。为了防止单条命令过大造成客户端缓冲区溢出,对于list
、set
、hash
、zset
等类型操作,以64个
元素为界拆分为多条。
重写过程的简化流程如下图所示:
- 1)执行AOF重写请求,如果当前进程正在执行AOF重写,请求不执行;如果当前进程正在执行
bgsave
操作,重写命令延迟到bgsave
完成之后再执行; - 2)父进程执行
fork
创建子进程,开销等同于bgsave
过程; - 3.1)主进程
fork
操作完成后,继续响应其他命令。所有修改命令
依然写入AOF缓冲区
并根据appendfsync策略
同步到硬盘,保证原有AOF机制正确性; - 3.2)由于
fork
操作运用写时复制
技术,子进程只能共享fork
操作时的内存数据。由于父进程依然响应命令,Redis使用AOF重写缓冲区
保存这部分新数据,防止新AOF文件生成期间丢失这部分数据; - 4)子进程根据内存快照,按照命令合并规则写入到
新的AOF文件
; - 5.1)新AOF文件写入完成后,子进程发送
信号
给父进程,父进程更新统计信息; - 5.2)父进程把
AOF重写缓冲区
的数据写入到新的AOF文件; - 5.3)使用新AOF文件
替换
老文件,完成AOF重写。
写时复制
(CopyOnWrite):顾名思义,在发生写操作
的时候,操作系统才会去复制
物理内存,这样是为了防止fork
创建子进程时,由于物理内存数据的复制时间过长而导致父进程长时间阻塞
的问题。信号
:是进程间通讯的一种方式,且是异步的
。
何时触发?
- 手动触发:直接调用
bgrewriteaof
命令。 - 自动触发:根据
auto-aof-rewrite-min-size
和auto-aof-rewrite-percentage
参数确定自动触发时机。auto-aof-rewrite-min-size
:表示运行AOF重写时文件最小体积,默认为64MB
。auto-aof-rewrite-percentage
:代表当前AOF文件空间(aof_current_size
)和上一次重写后AOF文件空间(aof_base_size
)的比值。
即,自动触发时机 =aof_current_size
>auto-aof-rewrite-min-size
&&(aof_current_size - aof_base_size
)/aof_base_size
>=auto-aof-rewrite-percentage
。
哪些地方会导致阻塞父进程?
- 创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;
- 创建完子进程后,如果子进程或者父进程
修改了共享数据
,就会发生写时复制
,这期间会拷贝物理内存,如果内存越大,自然阻塞的时间也越长,特别是bigkey
; - 信号处理函数执行时。
为什么AOF重写不复用原AOF日志?
- 父子进程写同一个文件会产生竞争问题,影响父进程的性能。
- 如果AOF重写过程中失败了,相当于污染了原本的AOF文件,无法做恢复数据使用。
总结下父进程的工作内容:
- 执行客户端发来的命令;
- 将执行后的
写命令
追加到AOF 缓冲区
;- 将执行后的
写命令
追加到AOF 重写缓冲区
;
2.2.4 重启加载
2.2.5 文件校验
加载损坏的AOF文件时会拒绝启动,并打印如下日志:
# Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>
两点建议:
- 对于
错误格式的AOF文件
,先进行备份,然后采用redis-check-aof--fix
命令进行修复,修复后使用diff -u
对比数据的差异,找出丢失的数据,有些可以人工修改补全。 - 对于
不完整
的情况,Redis为我们提供了aof-load-truncated
配置来兼容这种情况,默认开启
。加载AOF时,当遇到此问题时会忽略并继续启动,同时打印警告日志。#警告日志: # !!! Warning: short read while loading the AOF file !!! # !!! Truncating the AOF at offset 397856725 !!! # AOF loaded anyway because aof-load-truncated is enabled
3 RDB
RDB就是Redis DataBase
的缩写,中文名为快照/内存快照
,RDB持久化是把当前内存中的数据库快照
保存到磁盘
上的过程。
由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值。另外在 Redis 恢复数据
时, RDB恢复数据的效率会比 AOF 高些,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。
3.1 触发方式
分为手动触发
和自动触发
两种方式。
3.1.1 手动触发
save
命令:阻塞当前Redis服务器,直到RDB过程完成为止。bgsave
命令:Redis进程执行fork操作
创建子进程,RDB持久化过程由子进程
负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。
bgsave
具体流程如下:
- redis客户端执行
bgsave命令
或者自动触发bgsave命令
; - 主进程判断当前
是否已经存在
正在执行的子进程(如RDB/AOF子进程):- 如果存在,那么主进程直接返回;
- 如果不存在正在执行的子进程,那么就
fork
一个新的子进程进行持久化数据,fork过程是阻塞的,fork操作完成后主进程即可执行其他操作;
- 子进程先将数据写入到
临时的RDB文件
中,待快照数据写入完成后再原子替换旧的RDB文件
; - 同时发送
信号
给主进程,通知主进程RDB持久化完成,主进程更新相关的统计信息。
3.1.2 自动触发
有以下场景:
- 使用save相关配置,如
“save m n”
。表示m秒内
数据集存在n次
修改时,自动触发bgsave
。 - 如果
从节点
执行全量复制
操作,主节点自动执行bgsave
生成RDB文件并发送给从节点。 - 执行
debug reload
命令重新加载Redis时,也会自动触发save操作
。 - 默认情况下执行
shutdown
命令时,如果没有开启AOF持久化功能则自动执行bgsave
。
3.2 RDB优缺点
3.2.1 优点
- RDB文件是某个时间节点的快照,默认使用
LZF算法
进行压缩,压缩后的文件体积远远小于内存大小,适用于备份
、全量复制
等场景; - Redis加载RDB文件恢复数据要
远远快于
AOF方式。
3.2.2 缺点
- RDB方式
实时性不够
,无法做到秒级的持久化; - 每次调用
bgsave
都需要fork
子进程,fork子进程属于重量级操作
,频繁执行成本较高; - RDB文件是
二进制
的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全; - 版本兼容RDB文件问题。
针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。
4 混合
尽管 RDB 比 AOF 的数据恢复速度快,但是快照的频率不好把握:
- 如果频率太低,两次快照间一旦服务器发生宕机,就可能会比较多的数据丢失;
- 如果频率太高,频繁写入磁盘和创建子进程会带来额外的性能开销。
Redis 4.0
提出方法叫混合使用 AOF 日志和内存快照
,也叫混合持久化
。如果想要开启混合持久化功能,可以在 Redis 配置文件将下面这个配置项设置成yes
:
aof-use-rdb-preamble yes
混合持久化工作在 AOF 日志重写过程
。
- 当开启了混合持久化时,在 AOF 重写日志时,fork 出来的
重写子进程
会先将与主线程共享的内存数据以RDB 方式
写入到 AOF 文件; - 然后主线程处理的操作命令会被记录在
重写缓冲区
里,重写缓冲区里的增量命令
会以AOF 方式
写入到 AOF 文件; - 写入完成后通知主进程将新的含有
RDB 格式
和AOF 格式
的 AOF 文件替换旧的AOF 文件
。
也就是说,使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据。
4 参考
[^1] 张益军(付磊). 《Redis 开发与运维》4.1节。
[^2] Redis进阶 - 持久化:RDB和AOF机制详解
[^3] AOF 持久化是怎么实现的?