(三)Redis的持久化技术RDB和AOF

  因为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持久化

  举个例子,当我们开启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、age3个键的键值对保存到RDB文件中,而AOF持久化保存数据库状态的方法是将服务器执行的set、sadd、rpush3个命令保存到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属性,可以设置为alwayseverysecno,它们代表着不同的AOF文件保存方式

  • always:该模式下,服务器在每个事件循环后都要将写入进aof_buf缓冲区中的所有数据同步到AOF文件。虽然其效率是3个值中最慢的一个,但从安全行来说,它是最安全的,因为即使出现故障停机,AOF持久化也只会丢失一个事件循环中所产生的命令数据。
  • everysec(默认设置):该模式下,服务器每隔一秒将写入进aof_buf缓冲区中的所有数据同步到AOF文件。从效率上来说,该模式足够快,并且就算出现故障停机,数据库也只丢失一秒钟的命令数据。
  • no:该模式下,服务器在每个事件循环后将所有数据写入aof_buf缓冲区中,至于何时同步AOF文件,则由操作系统控制。因为该模式无需隔时间同步操作,所以写入速度是最快的。不过因为会在系统缓存中积累一段时间的写入数据,所以该模式的单次同步时长是最长的。但若服务机出现故障时,将丢失上次同步之后的所有写命令数据。
文件的同步(了解)

  虽然写入时的缓冲区机制提高了效率,但也带来了安全问题。如果计算机停机,那么保存在内存缓冲区里面的写入数据将会丢失。
  为此,系统提供了fsyncfdatasync两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。

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设计与实现》(第二版)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容