因为Redis是内存数据库,它将自己的数据库状态存储在内存当中,所以如果不想办法将存储在内存中的数据库状态保存到磁盘里面,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。
为了解决这个问题,Redis提供了RDB(Redis DataBase
)持久化功能和AOF(Append Only File
)持久化功能,都可以将Redis在内存中的数据库状态保存到磁盘里面,避免数据意外丢失。下面分别介绍:
RDB持久化
RDB持久化功能既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个RDB文件中,如图10-2:
RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态,如上图10-3。
因为RDB文件是保存在硬盘中的,所以即使Redis服务器进程退出,甚至运行Redis服务器的计算机停机,但只要RDB文件仍然存在,Redis服务器就可以用它来还原数据库状态。
RDB文件的创建
有两个Redis命令可以用于生成RDB文件,一个是SAVE
,另一个是BGSAVE
,如下:
127.0.0.1:6379> save //等待直到RDB文件创建完毕
OK
SAVE
命令会阻塞服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何客户端发送的命令请求。只有在服务器执行完;SAVE
命令后、重新开始接受命令请求之后,客户端发送的命令才会被处理。
127.0.0.1:6379> bgsave //派生子进程,并由子进程创建RDB文件
Background saving started
和SAVE
命令不同的是,BGSAVE
命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求,所以Redis服务器仍然可以继续处理客户端的命令请求。不过需要注意的是,BGSAVE
命令执行期间,客户端发送的SAVE
命令和BGSAVE
命令都会被服务器拒绝,这是为了防止产生竞争条件。还有BGSAVE
命令不能和BGREWRITEAOF
命令同时执行。
通过默认方式启动的服务器生成的RDB文件名默认为dump.rdb
,默认存放的位置可以通过config get dir
命令查看,不同系统的存储位置可能有偏差,macOS中dump.rdb
文件位置如下:
127.0.0.1:6379> config get dir
1) "dir"
2) "/Users/apple"
当然我们也可以以指定配置文件(redis.conf
)的方式启动服务器,所以你可以修改生成的RDB文件名字和位置,下面为配置文件中的默认配置(一般不修改)
# The filename where to dump the DB
dbfilename dump.rdb
# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir /usr/local/var/db/redis/
我们打开dump.rdb
文件查看一下:
REDIS0009� redis-ver�5.0.4�
redis-bits�@��ctime¾�\�used-mem����aof-preamble���i����=y
很显然是一堆有部分是看不懂的二进制字符,因为RDB文件本身就是是一个经过压缩的二进制文件,它默认有一定字节,保存了Redis版本等信息。
localhost:~ wksky$ od /usr/local/var/db/redis/dump.rdb
0000000 042522 044504 030123 030060 175071 071011 062145 071551
0000020 073055 071145 032405 030056 032056 005372 062562 064544
0000040 026563 064542 071564 040300 002772 072143 066551 141145
0000060 164447 056252 004372 071565 062145 066455 066545 110302
0000100 010007 175000 060414 063157 070055 062562 066541 066142
0000120 140145 177400 014442 141640 160140 073510
0000134
RDB文件的载入
和使用命令创建RDB文件不同,RDB文件的载入工作是在服务器启动时自动执行的,所以Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时监测到RDB文件存在,它就会自动载入RDB文件,下面为启动服务器时的日志:
4831:M 08 Apr 2019 10:46:15.806 # Server initialized
4831:M 08 Apr 2019 10:46:15.806 * DB loaded from disk: 0.000 seconds //载入RDB文件
4831:M 08 Apr 2019 10:46:15.806 * Ready to accept connections
根据Redis服务器不同的启动方式载入的RDB文件也不相同,默认方式启动载入的是/Users/apple
下的dump.rdb
文件(mac OS),以指定配置文件方式启动服务器载入的是配置文件中的RDB文件,即/usr/local/var/db/redis/
下的dump.rdb
文件(mac OS)。
还有需要注意的一点是:Redis服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。
自动间隔性保存
因为BGSAVE
命令可以在不阻塞服务器进程的情况下执行,所以Redis允许用户通过设置服务器配置的save
选项,让服务器每隔一段时间自动执行一次BGSAVE
命令。用户可以通过save
选项设置多个保存条件,只要其中任意一个条件被满足,服务器就会执行BGSAVE
命令。以下为redis.conf
文件中相关配置:
# Save the DB on disk:
#
# save <seconds> <changes>
#
# Will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
#
# In the example below the behaviour will be to save:
# after 900 sec (15 min) if at least 1 key changed
# after 300 sec (5 min) if at least 10 keys changed
# after 60 sec if at least 10000 keys changed
#
# Note: you can disable saving completely by commenting out all "save" lines.
#
# It is also possible to remove all the previously configured save
# points by adding a save directive with a single empty string argument
# like in the following example:
#
# save ""
save 900 1
save 300 10
save 60 10000
Redis的服务器周期性操作函数serverCron
默认每隔100毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,它的一项工作就是检查上面save
选项所设置的保存条件是否已经满足,如果满足的话,就执行BGSAVE
命令.
那么只要满足以下3个条件中的任何一个保存条件,BGSAVE
命令就会被执行:
- 服务器在900秒之内,对数据库进行了至少1次修改
- 服务器在300秒之内,对数据库进行了至少10次修改
- 服务器在60秒之内,对数据库进行了至少10000次修改
举个例子,以下是Redis服务器在60秒之内,对数据库进行了至少10000次修改之后,服务器自动执行BGSAVE
命令时打印出来的日志:
当然,以默认方式启动服务器的保存配置并不一样,不过相信你有了上面的理解,可以很容易的看懂它们,代码如下:
127.0.0.1:6379> config get save
1) "save"
2) "3600 1 300 100 60 10000"
AOF持久化
除了RDB外,Redis还提供了AOF持久化功能。与RDB持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的,如下图:
举个例子,当我们开启AOF持久化功能时(后面介绍),如果我们在数据库中执行下面3个命令,库中将包含3个键值对:
127.0.0.1:6379> set msg "hello world"
OK
127.0.0.1:6379> sadd fruits apple banana orange
(integer) 3
127.0.0.1:6379> rpush age 2 3 4
(integer) 3
RDB持久化保存数据库状态的方式是将msg、fruits、age
3个键的键值对保存到RDB文件中,而AOF持久化保存数据库状态的方法是将服务器执行的set、sadd、rpush
3个命令保存到AOF文件中。
被写入AOF文件的所以命令都是以Redis的命令请求协议格式保存的,为纯文本格式,和RDB的二进制不同,我们可以直接打开一个AOF文件appendonly.aof
,如下:
*2
$6
SELECT
$1
0
*3
$3
set
$3
msg
$11
hello world
*5
$4
sadd
$6
fruits
$5
apple
$6
banana
$6
orange
*5
$5
rpush
$3
age
$1
2
$1
3
$1
4
这个文件中,除了用于指定数据库的SELECT
命令是服务器自动添加的外,其他都是我们之前发送的命令,服务器启动时,可以通过载入和执行AOF文件中保存的命令来还原服务器关闭之前的数据库状态。
问题
:既然这个文件是纯文本格式,有一定格式,那我们是否能在其上增加一些命令或删除一些命令呢?放心,Redis有一定的检验机制,当发现该文件出现了错误文本时,会提示文件错误,请求修复。如果你按其指定格式来增加命令,该命令会被识别为错误命令,不会执行它。
AOF持久化的开启配置
AOF文件和RDB文件不同的是,以默认方式启动的Redis服务器是不开启AOF持久化技术的,我们以默认方式启动服务器并通过命令查看一下:
localhost:~ wksky$ redis-cli
127.0.0.1:6379> config get appendonly
1) "appendonly" //是否开启AOF持久化技术
2) "no" //不开启
127.0.0.1:6379> config get appendfilename
(empty list or set) //文件名也是空的
可以看到默认是不开启AOF持久化技术的,所以我们一般使用redis.conf
配置文件启动Redis服务器,将其appendonly
状态改为yes即可,下面为redis.conf
中配置信息:
############################## APPEND ONLY MODE ###############################
# By default Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# a power outage may result into a few minutes of writes lost (depending on
# the configured save points).
#
# The Append Only File is an alternative persistence mode that provides
# much better durability. For instance using the default data fsync policy
# (see later in the config file) Redis can lose just one second of writes in a
# dramatic event like a server power outage, or a single write if something
# wrong with the Redis process itself happens, but the operating system is
# still running correctly.
#
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.
#appendonly no
appendonly yes
# The name of the append only file (default: "appendonly.aof")
appendfilename "appendonly.aof"
appendfilename
文件名默认为"appendonly.aof"
就好,其文件保存位置和RDB文件保存的位置一样。
AOF持久化的实现
AOF持久化功能的实现可以分为命令追加(append
)、文件写入、文件同步(sync
)3个步骤.
命令追加
当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf
缓冲区的末尾。
文件的写入
Redis的服务器进程就是一个事件循环(loop),这个循环中的文件事件负责接受客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像serveerCron
函数这样需要定期运行的函数。
为了提高文件的写入效率,在现代操作系统中,当用户调用write
函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区中,等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面(写入 = 数据进内存缓冲区, 同步 = 缓冲区数据进硬盘
)。
因为服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf
缓冲区里面,所以在服务器每次结束一个事件循环之前,它都会调用flushAppendOnlyFile
函数,考虑是否需要将aof_buf
缓冲区的内容写入和保存到AOF文件里面,它的行为由redis.conf
设置的appendfsync
属性来决定,配置如下。
# The fsync() call tells the Operating System to actually write data on disk
# instead of waiting for more data in the output buffer. Some OS will really flush
# data on disk, some other OS will just try to do it ASAP.
#
# Redis supports three different modes:
#
# no: don't fsync, just let the OS flush the data when it wants. Faster.
# always: fsync after every write to the append only log. Slow, Safest.
# everysec: fsync only one time every second. Compromise.
#
# The default is "everysec", as that's usually the right compromise between
# speed and data safety. It's up to you to understand if you can relax this to
# "no" that will let the operating system flush the output buffer when
# it wants, for better performances (but if you can live with the idea of
# some data loss consider the default persistence mode that's snapshotting),
# or on the contrary, use "always" that's very slow but a bit safer than
# everysec.
#
# More details please check the following article:
# http://antirez.com/post/redis-persistence-demystified.html
#
# If unsure, use "everysec".
# appendfsync always
appendfsync everysec
# appendfsync no
其中的appendfsync
属性,可以设置为always
、everysec
、no
,它们代表着不同的AOF文件保存方式
-
always
:该模式下,服务器在每个事件循环后都要将写入进aof_buf缓冲区中的所有数据同步到AOF文件。虽然其效率是3个值中最慢的一个,但从安全行来说,它是最安全的,因为即使出现故障停机,AOF持久化也只会丢失一个事件循环中所产生的命令数据。 -
everysec(默认设置)
:该模式下,服务器每隔一秒将写入进aof_buf缓冲区中的所有数据同步到AOF文件。从效率上来说,该模式足够快,并且就算出现故障停机,数据库也只丢失一秒钟的命令数据。 -
no
:该模式下,服务器在每个事件循环后将所有数据写入aof_buf缓冲区中,至于何时同步AOF文件,则由操作系统控制。因为该模式无需隔时间同步操作,所以写入速度是最快的。不过因为会在系统缓存中积累一段时间的写入数据,所以该模式的单次同步时长是最长的。但若服务机出现故障时,将丢失上次同步之后的所有写命令数据。
文件的同步(了解)
虽然写入时的缓冲区机制提高了效率,但也带来了安全问题。如果计算机停机,那么保存在内存缓冲区里面的写入数据将会丢失。
为此,系统提供了fsync
和fdatasync
两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。
AOF文件的载入与数据还原
因为AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令,即可还原服务器关闭之前的数据库状态。
AOF重写
因为AOF持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF文件中的内容会越来越多,文件的体积也越来越大。如果不加以控制,可能对Redis服务器、甚至整个宿主机造成影响,而且文件体积越大,进行数据还原的所需的时间就越来越多。
为了解决这个问题,Redis提供了AOF文件重写(rewrite
)功能,类似于压缩。通过该功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个文件保存的数据库状态相同,但新的体积更小。通过一个例子了解一下:
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> incr age
(integer) 19
127.0.0.1:6379> incr age
(integer) 20
当执行完上面命令后,会生成一个appendonly.aof
文件,内容如下:
*2
$6
SELECT
$1
0
*3
$3
set
$3
age
$2
18
*2
$4
incr
$3
age
*2
$4
incr
$3
age
当我们执行重写命令bgrewriteaof
后:
127.0.0.1:6379> bgrewriteaof
appendonly.aof
变为如下格式:
REDIS0009� redis-ver�5.0.4�
redis-bits�@��ctime�m2�\�used-mem�P��aof-preamble������age�����m1��ͧ
是不是感觉似曾相识?它变为二进制文件了。
我们使用的是后台重写,原因和RDB文件保存和后台保存类似,不仅可以防止线程被长时间阻塞,还可以避免使用锁来保证数据的安全性。
可是在重写期间,若服务器进程还继续处理命令请求,新命令可能对现有数据库进行更改,会使得服务器当前状态和重写后的状态不一致。这该如何解决?为了解决数据不一致问题,Redis服务器设置了一个AOF重写缓冲区,在服务器创建子进程之后使用,当Redis服务器执行完一个写命令之后,会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区,保证了数据的一致性,如下图:
两种持久化技术的总结
emsp;因为AOF文件的更新频率通常比RDB文件的更新频率高,所以:
- 如果服务器开启了AOF持久化功能,服务器会优先使用AOF文件来还原数据库状态
- 只有在AOF持久化功能处于关闭状态,服务器才使用RDB文件来还原数据库状态。
RDB的优点
- 节省磁盘空间
- 恢复速度快
RDB的缺点
- 虽然Redis在fork时使用了写时拷贝技术,但如果数据庞大时还是比较消耗性能
- 一定时间间隔做一次备份,如果服务器意外宕机,就会丢失最后一次快照后的所有修改
AOF的优点
- 备份机制更稳定,丢失数据概率更低
- 可读性强的日志文本,可以处理误操作
AOF的缺点
- 比RDB耗费更多磁盘空间
- 每次读写都同步会有一定的性能压力
- 存在个别Bug,造成无法恢复的情况
用哪个好
- 官方推荐两个都用
- 如果对数据不敏感,可以选单独用RDB
- 如果只是做纯内存缓存,可以都不使用
- 不建议单独用AOF,因为可能出现Bug
参考资料
《redis设计与实现》(第二版)