Redis与传统数据的一个主要区别就是Redis将所有的数据都存储在内存中,而传统数据库通常只会把数据存储到磁盘上,这就使得Redis的数据存储和读取有着极快的速度,但是由于内存属于易失存储器,它记录的所有数据一旦断电就消失了,这对于想把Redis作为数据库而不仅仅是缓存的用户来说是不能容忍的。
为了满足不同场景下的持久化需求,Redis提供了RDB(Redis DataBase)持久化、AOF(Append Only File)持久化和RDB-AOF混和持久化等多种持久化机制。如果用户不需要持久化,也可以完全关闭持久化功能。
一、RDB(Redis DataBase)持久化
RDB持久化是Redis默认使用的持久化方式,它是将当前Redis进程中的数据生成快照保存到硬盘,默认会在当前工作目录下生成dump.rdb
文件,当Redis重启的时候会自动读取快照文件来恢复数据。
Redis提供了多种创建RDB文件的方法,用户既可以使用SAVE命令或BGSAVE命令手动创建RDB文件,也可以在redis.conf配置文件中设置save来配置选项让服务器在满足指定条件后自动触发BGSAVE命令。
1、手动输入命令创建RDB文件

SAVE命令和BGSAVE命令都可以生成RDB文件。SAVE和BGSAVE的区别是:
-
SAVE:接收到SAVE命令后,Redis服务器将遍历数据库包含所有数据库,并将各个数据库包含的键值对全部记录到RDB文件中。在SAVE命令执行期间,Redis服务器将阻塞,直到RDB文件创建完毕为止。如果在创建RDB文件时已经有了对应的RDB文件,那么服务器将会新创建RDB文件代替已有的RD文件。
-
BGSAVE:BGSAVE是SAVE命令的异步版本,当Redis服务器收到BGSAVE命令是,将会执行以下操作: 1)创建一个子进程(拷贝一份父进程)。 2)子进程执行SAVE命令,创建RDB文件。 3)RDB文件创建完毕之后,子进程退出并通知Redis服务器进程(父进程)RDB文件已经创建完毕。
一般来说,在生产环境很少直接使用 SAVE 命令,因为它会阻塞Redis服务器进程,保存RDB文件的任务通常由 BGSAVE 命令异步地执行。然而,如果负责保存数据的后台子进程不幸出现问题时, SAVE 可以作为保存数据的最后手段来使用。
2、通过配置选项自动创建RDB文件

用户除了可以使用SAVE命令和BGSAVE命令手动创建RDB文件之外,还可以通过在配置文件中设置save选项,让Redis服务器在满足指定条件后自动触发BGSAVE命令:
save <seconds> <changes>
save选项接收两个参数:seconds和changes,即服务器的数据只要在指定的seconds秒内只要发生了changes次变化后,就会触发BESAVE。Redis默认的配置有三个:
save 900 1 //900秒内Redis数据发生了至少1次变化,则执行bgsave
save 300 10 //300秒内Redis数据发生了至少10次变化,则执行bgsave
save 60 10000 //60秒内Redis数据发生了至少10000次变化,则执行bgsave
注意,为了避免由于同时使用多个触发条件而导致服务器频繁的执行BGSAVE命令,Redis服务器会在每次成功创建RDB文件之后将负责自动触发BGSAVE命令的时间计数器清0并重新开始计数:无论这个RDB文件是由自动触发的BGSAVE命令创建的,还是有用户执行的SAVE或BGSAVE命令创建的,都是如此。
3、自动触发的原理
Redis的自动触发是通过serverCron函数、dirty计数器和lastave时间戳来实现的。
-
serverCron是Redis服务器的周期性操作函数,默认每隔100ms执行一次;该函数对服务器的状态进行维护,其中一项工作就是检查
save <seconds> <changes>
配置的条件是否满足,如果满足就执行bgsave。 -
dirty计数器是Redis服务器维持的一个状态,记录了上一次执行bgsave/save命令后,服务器状态进行了多少次修改(包括增删改);而当save/bgsave执行完成后,会将dirty重新置为0。
-
lastsave时间戳也是Redis服务器维持的一个状态,记录的是上一次成功执行save/bgsave的时间。
也就是每隔100ms,执行serverCron函数;在serverCron函数中,遍历save <seconds> <changes>
配置的保存条件,只要有一个条件满足,就进行bgsave。对于每一个save <seconds> <changes>
条件,只有下面两条同时满足时才算满足: (1)当前时间-lastsave > seconds (2)dirty >= changes
4、其他自动触发机制
除了在配置文件中配置save <seconds> <changes>
来触发BGSAVE以外,还有别的情况会触发BGSAVE:
- 在主从复制场景下,如果从结点执行全量复制操作,则主结点会执行BGSAVE命令,并将dump.rdb文件发送给从结点。
- 在执行
shutdown
命令时,会自动执行RDB持久化,这一点通过redis的日志看到

5、RDB文件结构
RDB的文件格式 RDB文件是经过压缩的二进制文件,RDB的的文件格式如下图所示

其中重要字段的含义说明如下:
-
Redis文件标识符:文件最开头是RDB文件标识符,这个标识符内容为"REDIS"5个字符。Redis会在尝试加载RDB文件的时候通过标识符快速判断这是不是一个RDB文件。
-
版本号:跟在RDB文件标识符后面的是RDB文件的版本号,这个版本号是一个字符串数字,长度为4个字符。比如Redis 5.0 的RDB版本号是"0009",不同版本的RDB文件结构会有些许不同,但是新版的RDB都会在旧版本上添加更多信息,而且新版的Redis服务器总是能够向下兼容旧版的Redis服务器生成的RDB文件。比如在Redis 5.0的服务器可以正常读取Redis 4.0生成的"0008"版本的RDB文件。
-
数据库数据:SELECTDB 0 pairs 表示一个完整的数据库(0号数据库),同理SELECTDB 3 pairs表示3号数据库;只有当数据库中有键值对时,RDB文件中才会有该数据库的信息(上图所示的Redis中只有0号和3号数据库有键值对);如果Redis中所有的数据库都没有键值对,则这一部分直接省略。其中:SELECTDB是一个常量,代表后面跟着的是数据库号码;0和3是数据库号码;pairs则存储了具体的键值对信息,包括key、value值,及其数据类型、内部编码、过期时间、压缩信息等等。
-
EOF:常量,标志RDB文件正文内容结束,他的实际值为二进制0xFF。当Redis读取到EOF,它就知道RDB文件的正文部分已经全部读取完毕了。
-
CRC64校验和:RDB文件的末尾是一个一无符号64整数表示的CRC64校验和,在载入RBD文件时,会重新计算校验和并与CRC64值比较,判断文件是否损坏。
RDB文件的存储路径
RDB文件的存储路径既可以在redis.conf配置文件中配置,也可以在客户端通过命令动态设定:
- 在配置文件中可以设置
dir
来指定RDB文件的存放路径,redis默认是存放在当前工作目录下。也可以在配置文件中通过设置dbfilename
指定RDB文件的名字,redis默认的文件名是dump
。 - 动态设定:Redis启动后也可以在客户端使用
config set dir /path
来动态的改变RDB的存放路径,当然也可以通过config set dbfilename newfilenaem
来设置RDB文件的名字。
RDB文件的压缩
Redis默认采用LZF算法对RDB文件进行压缩。虽然压缩会有一定的性能消耗,但是这样可以大大减小RDB文件的大小。但是需要特别注意的是:RDB文件的压缩并不是针对整个文件进行的,而是对数据库中的字符进行的,且只有在字符串达到一定长度(20字节)时才会进行压缩。
6、载入RDB文件
RDB文件的载入工作是在服务器启动的时候自动进行的,并没有专门的命令。但是当开启AOF后,由于AOF文件的优先级更高,Redis会优先加载AOF文件来恢复数据,只有当AOF关闭时,才会在Redis服务器启动的时候检测RDB文件,并自动加载。服务器载入RDB文件期间处于阻塞状态,直到加载完毕阻塞解除。Redis载入RDB文件时,会对RDB文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败。具体步骤如下:
首先在工作目录中寻找是否有RDB文件出现,如果有就打开它,,然后读取文件的内容并执行以下载入操作
- 1) 检查文件开头的表示符是否是"REDIS",如果是则据需后续载入操作,不是则抛出错误终止载入操作。
- 2) 检查RDB文件版本号,判断当前Redis服务器能否读取这一版本的RDB文件。
- 3) 根据RDB文件中记录的设备附加信息,执行相应的操作和设置。
- 4) 检查数据库数据部分是否为空,如果不为空着执行以下操作
- (1)根据文件记录的数据库号码,切换到对应数据库。
- (2)根据文件记录的键值对总数量以及带有过期时间的监视对数量,设置数据库底层数据结构。
- (3)一个接一个的载入文件记录的所有键值对数据,并在数据库中重建这些键值对。
- 5) 如果服务器启动了复制功能,那么将之前缓存的Lua脚本重新载入缓存中。
- 6) 遇到EOF结束标记符,确认RDB文件已经全部读取完毕。
- 7) 载入CRC64校验和,把它和载入数据期间计算出的CRC64比对,以此判断被载入的数据是否完好。
- 8) RDB文件载入完毕,等待客户端的请求。
7、RDB优缺点总结
最后以一幅图的方式总结RDB的优缺点:

二、AOF(Append Only File)持久化方式
与全量式的RDB持久化功能不同,AOF提供的是增量式持久化功能,这种持久化的核心原理在于:服务器每次执行完写命令之后,都会将命令追加到AOF文件尾部。这样一来服务器停机之后,只要重新执行AOF文件中保存的Redis命令,就可以将数据恢复值停机之前的状态。与RDB相比较AOF具有更好的实时性,也是当前主流的持久化方案
1、开启AOF持久化功能
Redis服务器默认只开启了RDB持久化方式,要开启AOF,需要在redis.conf中修改appendonly
为yes
,并且还可以在配置文件中修改AOF文件的名字等等,具体的可以参考我的这篇笔记:Redis配置文件redis.conf详解

2、AOF的执行流程
AOF的执行流程包括:
- 命令追加(append):将Redis的写操作追加到缓冲区aof_buf
- 文件写入(write)和文件同步(sync):根据不同的同步策略将aof_buf中的内容同步带硬盘
- 文件重写(rewrite):当AOF文件过大的时候重写AOF文件,达到压缩的目的。
命令追加(append)
Redis先将写命令追加到缓冲区,而不是直接写入文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈。 命令追加的格式是Redis命令请求的协议格式,它是一种纯文本格式,具有兼容性好、可读性强、容易处理、操作简单避免二次开销等优点。在AOF文件中,除了用于指定数据库的select命令(如select 0 为选中0号数据库)是由Redis添加的,其他都是客户端发送来的写命令。
文件写入(write)和文件同步(sync)
Redis提供了多种AOF缓存区的同步文件策略,策略涉及到操作系统的write函数和fsync函数:
write函数
:为了提高文件写入效率,在现代操作系统中,当用户调用write函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。这样的操作虽然提高了效率,但也带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失fsync函数
:为了解决write函数数据丢失的问题,因此系统提供了fsync、fdatasync等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。
为了消除操作系统的写缓存机制带来的不确定性,Redis向用户提供了appendfsync选项,以此来控制系统写AOF的频率:
appendfsync <value>
appendfsync选项拥有always、no、everysec 3个值可选,他们代表的含义分别为:
always
命令写入aof_buf后立即调用系统的fsync函数同步到AOF文件,fsync完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。

no
命令写入aof_buf后调用系统的wirte函数,不对AOF文件做fsync同步,同步操作由系统负责,通常同步周期为30s。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。

everysec
命令写入aof_buf后调用系统的write函数,write完成后返回,fsync同步文件操作,有专门的线程每一秒调用一次。everysec是前面两种策略的折中,兼顾了性能和数据安全,也是Redis的默认配置。

3、AOF文件重写(rewrite)
AOF文件重写主要的作用就是对AOF文件进行压缩,减小AOF文件的体积。需要注意的是,AOF重写只会把Redis进程内的数据转化为写命令,同步到新的AOF文件;不会对旧的AOF文件进行任何读取、写入操作。

为什么文件重写可以压缩AOF文件?
-
- 过期的数据不需要再写入文件
-
- 无效的命令不再写入文件
-
- 多条命令可以合并为一条命令,比如
sadd stu v1
,sadd stu v2
,sadd stu v2
,这三条操作可以合并为一条sadd stu v1 v2 v3
。不过为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset类型的key,并不一定只使用一条命令;而是以某个常量为界将命令拆分为多条。这个常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定义
- 多条命令可以合并为一条命令,比如
总之压缩的原理就是通过重写减小命令的数量从而减少了文件的大小。
文件重写的触发
用户可以通过执行BGREWRITEAOF命令或者在配置文件中设置对应的选项来触发AOF文件重写操作。
1、BGREWRITEAOF命令
用户可以通过在Redis客户端手动指定BGREWRITEAOF命令显式的触发AOF操作,该命令是一个无参数命令。BGREWRITEAOF命令是一个异步命令,Redis服务器在接收到命令后会创建一个子进程,由他扫描数据库并生成新的AOF文件。当新的AOF文件生成完毕,子进程就会退出并通知主进程,然后主进程会使用新的AOF文件代替旧旧的AOF文件。

BGREWRITEAOF命令执行时服务器的日志
另外关于BGREWRITEAOF命令需要注意两点:首先,如果用户发送BGREWRITEAOF命令请求时,服务器正在创建RDB文件,那么服务器将会把AOF的重写操作延后到RDB文件创建完成之后再执行,以此避免两个写操作同时执行导致性能的下降;其次,如果服务器在执行重写的过程中,又接收到了新的BGREWRITEAOF命令请求,那么服务器将会返回错误信息。
2、AOF重写配置选项
除了可以手动执行BGREWRITEAOF命令重写AOF文件之外,还可以通过配置选项自动的触发BGREWRITEAOF命令重写,主要配置的参数如下:
auto-aof-rewrite-min-size <value>:执行AOF重写时,文件体积最小体积,默认为64MB。
auto-aof-rewrite-percentage <value>:执行AOF重写时,当前AOF大小和上一次重写AOF大小的比值,默认大小100。这些参数都可通过`config get 参数`来查看。
其中 auto-aof-rewrite-min-size 用于设置自动触发BGREWRITEAOF命令的最小AOF文件体积,当AOF文件的体积喜小于给定值时,服务器将不会自动执行BGREWRITEAOF命令。默认值是64MB,含义就是如果AOF文件的体积超过64MB后就会自动触发BGREWRITEAOF命令执行AOF重写。
另一个选项 auto-aof-rewrite-percentage 它控制的是触发自动AOF文件重写所需的文件体积增大比例。默认值是100,表示如果当前AOF文件的体积比最后一次AOF文件重写后的体积增大了一倍(100%),那将自动触发BGREWRITEAOF命令执行AOF重写。如果之前还没有执行过重写,那么服务器启动时的AOF文件大小会被当做最后AOF重写的体积。

文件重写的流程

对照上图,可以总结出AOF文件的重写流程如下:
-
Redis父进程首先判断当前是否存在正在执行 bgsave/bgrewriteaof的子进程,如果存在则bgrewriteaof命令直接返回,如果存在bgsave命令则等bgsave执行完成后再执行。
-
父进程执行fork操作创建子进程,这个过程中会阻塞Redis主进程。
-
父进程fork后,执行bgrewriteaof命令返回”Background append only file rewrite started”信息并不再阻塞父进程,此时Redis主进程恢复可以响应其他命令。Redis的所有写命令依然写入AOF缓冲区,并根据appendfsync策略同步到硬盘,保证原有AOF机制的正确。由于fork操作使用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令,因此Redis使用AOF重写缓冲区(图中的aof_rewrite_buf)保存这部分数据,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof执行期间,Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。
-
子进程根据内存快照,按照命令合并规则写入到新的AOF文件。
-
子进程重写完新的AOF文件后,向父进程发信号,父进程更新统计信息,具体可以通过info persistence查看。 接着父进程把AOF重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。
4、AOF文件启动加载
当开启AOF后,Redis重启会默认优先加载AOF文件来恢复数据;只有当AOF关闭时参会加载RDB文件。Redis加载AOF文件时,会对AOF文件进行校验,如果文件损坏,则日志中就会打印错误,并且Redis会启动失败。当AOF文件损坏后,我们可以使用redis-check-aof
这个工具来修复AOF文件。
5、AOF优缺点总结
最后以一幅图片总结AOF的优缺点:

三、RDB-AOF混合持久化
在前面我们分析了Redis两种持久化方式,他们都有各自的优缺点:
- RDB持久化可以生成紧凑RDB文件,并且使用RDB文件进行数据恢复的速度也非常快,但是RDB的全量持久化模式可能会让服务器在宕机是丢失大量数据。
- 与RDB相比,AOF持久化可以将丢失数据的时间窗口限制在1s内,但是AOF文件的体积要比RDB文件的体积大的多,并且数据恢复过程也相对较慢。
为了解决RDB和AOF两种持久化方式选择问题,Redis从4.0版本开始引入RDB-AOF混合持久化模式,这种模式是基于AOF持久化模式构建而来的,在打开AOF持久化的基础上,在配置上一下信息就可以打开混合持久化:
aof-use-rdb-preamble <value>
选项如果设置成为了yes,那么Redis服务器将在执行AOF重写操作时,向执行BGSAVE命令那样,根据数据库当前状态生成出相应的RDB数据,并将这些数据写入新建的AOF文件。换句话说,如果开启了混合持久化功能之后,服务器生成的AOF文件将由廊部分组成,其中AOF开头的是RDB格式的数据,

RDB-AOF混合持久化生成的AOF文件
通过使用RDB-AOF混合持久化功能,用户可以同时获得RDB持久化和AOF持久化的优点:服务器既可以通过AOF文件包含的RDB数据快速恢复数据,有可以通过AOF文件包含的AOF数据来讲数据丢失控制到1s之内。
需要注意的是,RDB-AOF混合持久化生成的AOF文件同时包含RDB格式的数据和AOF个格式的数据,而传统的AOF持久化生成的AOF文件只包含AOF格式的数据。
四、同时使用RDB持久化和AOF持久化
在Redis 4.0的RDB-AOF混合持久化出现之前,许多追求安全性的Redis使用者都会同时开启RDB和AOF阆中持久化方式,但是随着Redis 4.0的RDB-AOF混合持久化的推出,同时使用两种持久化机制已经没有必要。对于使用Redis 4.0的系统,优先使用RDB-AOF混合持久化是个不错的选择。
如果使用的是Redis 4.0 之前的版本,那么在RDB和AOF之间如何选择,下面是Redis官方给出的建议:原文链接
我的理解就是Redis 4.0版本之前具体选择哪种持久化方式要看你的业务类型,如果你的业务对于数据一致性没有那么高的要求、网站访问量非常有限,那么仅仅开启RDB就足够了,比如像我的这个博客网站;但是如果你的业务对数据一致性要求非常高、网站访问量有十分巨大,那么RDB配合AOF是个不错的选择。
特别的,Redis官方建议不要单独使用AOF,因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。