logo头像
Snippet 博客主题

Redis持久化机制

1. Redis持久化

Redis作为内存数据库,极大的提高了数据的读写效率,这一点毋庸置疑。但是内存作为一个易失存储器,所有的数据在断电后就会丢失,这些在我们看来是不能够接受的。

为了解决以上问题,Redis为我们提供了不同的持久性选项范围:

  • RDB持久性按指定的时间间隔执行数据集的时间点快照。
  • AOF持久性会记录服务器接收的每个写入操作,这些操作将在服务器启动时再次播放,以重建原始数据集。使用与Redis协议本身相同的格式记录命令,并且仅采用追加方式。当日志太大时,Redis可以在后台重写日志。
  • 如果希望,只要您的数据在服务器运行时就一直存在,则可以完全禁用持久性。
  • 可以在同一实例中同时合并AOF和RDB。请注意,在这种情况下,当Redis重新启动时,AOF文件将用于重建原始数据集,因为它可以保证是最完整的。

1.1 RDB持久化

1.1.1 RDB简述

Redis默认的持久化是RDB方式,RDB又名持久化快照,该功能可以创建出一个经过压缩的二进制文件(dump.rdb)。

当 Redis 需要做持久化时,Redis 会 fork 一个子进程,子进程将数据写到磁盘上一个临时 RDB 文件中。

当子进程完成写临时文件后,将原来的 RDB 替换掉,这样的好处是可以 copy-on-write。

1.1.2 创建RDB文件的三种方式

  • 通过SAVE命令创建RDB文件

    • 通过执行SAVE命令以同步方式创建出当前Redis服务器所有数据库数据的RDB文件。
    • 在SAVE命令执行期间,Redis服务将阻塞客户端服务,直到创建完成后恢复服务。
    • 若已经有相应的RDB文件,执行SAVE后,移除旧文件,保留新文件。
    • 复杂度:O(N),其中N为Redis服务器所有数据库包含的键值对总数量。
  • 通过BGSAVE命令创建RDB文件

    • 异步创建RDB文件,不会直接通过Redis服务进程创建,而是fork子进程创建,不阻塞客户端服务,
    • 当执行BGSAVE命令后,创建一个子进程,子进程执行SAVE命令,创建最新的RDB快照,创建完毕后,子进程退出并通知父进程替换为最新的RDB快照。
    • 执行BGSAVE后,父进程占用的内存数量越大,创建子进程耗费的时间越长,仍然可能会有短暂地阻塞。
    • 复杂度:O(N),其中N为Redis服务器所有数据库包含的键值对总数量。
    • 离线Redis服务器用SAVE保存快照效率高于BGSAVE。
  • 通过配置选项自动创建RDB文件

    我们打开redis.conf找到SNAPSHOTTING 位置,我们可以详细的看出RDB快照配置说明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    ################################ SNAPSHOTTING  ################################
    #
    # Save the DB on disk: 保存DB到磁盘
    #
    # save <seconds> <changes> 参数选项: save 秒数 秒数之内key的changes数量
    #
    # Will save the DB if both the given number of seconds and the given
    # number of write operations against the DB occurred. 若数据库达到seconds和changes数时,保存一次快照文件
    #
    # 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 "" # 通过配置空串关闭RDB模式

    save 900 1 # 默认900秒(15分钟)之内至少有1个key被changed触发快照保存
    save 300 10 # 默认900秒(5分钟)之内至少有10个key被changed触发快照保存
    save 60 10000 # 默认900秒(1分钟)之内至少有100000个key被changed触发快照保存

    # By default Redis will stop accepting writes if RDB snapshots are enabled
    # (at least one save point) and the latest background save failed.
    # This will make the user aware (in a hard way) that data is not persisting
    # on disk properly, otherwise chances are that no one will notice and some
    # disaster will happen.
    #
    # If the background saving process will start working again Redis will
    # automatically allow writes again.
    #
    # However if you have setup your proper monitoring of the Redis server
    # and persistence, you may want to disable this feature so that Redis will
    # continue to work as usual even if there are problems with disk,
    # permissions, and so forth.
    stop-writes-on-bgsave-error yes

    # Compress string objects using LZF when dump .rdb databases?
    # For default that's set to 'yes' as it's almost always a win.
    # If you want to save some CPU in the saving child set it to 'no' but
    # the dataset will likely be bigger if you have compressible values or keys.
    rdbcompression yes RDB文件压缩

    # Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
    # This makes the format more resistant to corruption but there is a performance
    # hit to pay (around 10%) when saving and loading RDB files, so you can disable it
    # for maximum performances.
    #
    # RDB files created with checksum disabled have a checksum of zero that will
    # tell the loading code to skip the check.
    rdbchecksum yes 是否校验RDB的完整性

    # The filename where to dump the DB RDB存储的文件名称
    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 ./ RDB的存储工作目录

1.1.3 RDB文件结构

我们打开dump.rdb文件,我们可以分析一下RDB文件的组成结构:

1
2
REDIS0009ъ	redis-ver5.0.8ъ
redis-bitsј@ъctime¬«ѓС^ъused-mem¬јa(
  • RDB文件标识符

    ​ RDB文件标识符文件最开头的部分为RDB文件标识符,这个标识符的内容为”REDIS”这5个字符。Redis服务器在尝试载入RDB文件的时候,可以通过这个标识符快速地判断该文件是否为真正的RDB文件。

  • 版本号

    ​ 跟在RDB文件标识符之后的是RDB文件的版本号,这个版本号是一个字符串格式的数字,长度为4个字符。

    目前最新的RDB文件版本为第9版,因此RDB文件的版本号将为字符串”0009”。

    ​ 不同版本的RDB文件在结构上都会有一些不同,总的来说,新版RDB文件都会在旧版RDB文件的基础上添加更多信息,因此RDB文件的版本越新,RDB文件的结构就越复杂。关于RDB文件,需要说明的另外一点是新版Redis服务器总是能够向下兼容旧版Redis服务器生成的RDB文件。

    ​ 比如,生成第9版RDB文件的Redis 5.0既能够正常读入由Redis 4.0生成的第8版RDB文件,也能够读入由Redis 3.2生成的第7版RDB文件,甚至更旧版本的RDB文件也是可以的。

    ​ 与此相反,如果Redis服务器生成的是较旧版本的RDB文件,那么它是无法读入更新版本的RDB文件的。比如,生成第8版RDB文件的Redis 4.0就不能读入由Redis 5.0生成的第9版RDB文件。

  • 设备附加信息

    ​ RDB文件的设备附加信息部分记录了生成RDB文件的Redis服务器及其所在平台的信息,比如服务器的版本号、宿主机器的架构、创建RDB文件时的时间戳、服务器占用的内存数量等。

  • 数据库数据

    ​ RDB文件的数据库数据部分记录了Redis服务器存储的0个或任意多个数据库的数据,当这个部分包含多数个数据库的数据时,各个数据库的数据将按照数据库号码从小到大进行排列,比如,0号数据库的数据将排在最前面,紧接着是1号数据库的数据,然后是2号数据库的数据,以此类推。

    ​ 每个数据库都由数据库号码、键值对总数量、带有过期时间的键值对数量、键值对数据部分。

  • Lua脚本缓存

    ​ 如果Redis服务器启用了复制功能,那么服务器将在RDB文件的Lua脚本缓存部分保存所有已被缓存的Lua脚本。这样一来,从服务器在载入RDB文件完成数据同步之后,就可以继续执行主服务器发来的EVALSHA命令了。

  • EOF

    ​ RDB文件的EOF部分用于标识RDB正文内容的末尾,它的实际值为二进制值0xFF。当Redis服务器读取到EOF的时候,它知道RDB文件的正文部分已经全部读取完毕了。

  • CRC64校验和

    ​ RDB文件的末尾是一个以无符号64位整数表示的CRC64校验和,比如5097628732947693614。Redis服务器在读入RDB文件时会通过这个校验和来快速地检查RDB文件是否有出错或者损坏的情况出现。

1.1.4 RDB文件载入恢复

  • 当Redis服务器启动时,它会在工作目录中查找是否有RDB文件出现,如果有就打开它,然后读取文件的内容并执行载入操作。
  • 检查文件开头的标识符是否为”REDIS”,如果是则继续执行后续的载入操作,不是则抛出错误并终止载入操作。
  • 检查文件的RDB版本号,以此来判断当前Redis服务器能否读取这一版本的RDB文件。
  • 根据文件中记录的设备附加信息,执行相应的操作和设置。
  • 检查文件的数据库数据部分是否为空,如果不为空就执行以下子操作:
    • 根据文件记录的数据库号码,切换至正确的数据库。
    • 根据文件记录的键值对总数量以及带有过期时间的键值对数量,设置数据库底层数据结构。
    • 一个接一个地载入文件记录的所有键值对数据,并在数据库中重建这些键值对。
  • 如果服务器启用了复制功能,那么将之前缓存的Lua脚本重新载入缓存中。
  • 遇到EOF标识,确认RDB正文已经全部读取完毕。
  • 载入RDB文件末尾记录的CRC64校验和,把它与载入数据期间计算出的CRC64校验和进行对比,以此来判断被载入的数据是否完好无损。
  • RDB文件载入完毕,服务器开始接受客户端请求。

1.1.5 RDB持久化优缺点

优点:

  • RDB非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如云服务器。
  • RDB对redis对外提供的读写服务,影响非常小,可以让redis保持高性能,因为redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可
  • 相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复redis进程,更加快速。

缺点:

  • 如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。因为SAVE命令是一个同步操作,它的开始和结束都位于同一个原子时间之内,所以如果用户使用SAVE命令进行持久化,那么服务器在停机时将丢失最后一次成功执行SAVE命令之后产生的所有数据。无论用户使用的是SAVE命令还是BGSAVE命令,停机时服务器丢失的数据量将取决于创建RDB文件的时间间隔:间隔越长,停机时丢失的数据也就越多。
  • RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,Fork子进程可能很耗时,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。

1.2 AOF持久化

1.2.1 AOF简述

与全量式的RDB持久化功能不同,AOF提供的是增量式的持久化功能,这种持久化的核心原理在于:

服务器每次执行完写命令之后,都会以协议文本的方式将被执行的命令追加到AOF文件的末尾。这样一来,服务器在停机之后,只要重新执行AOF文件中保存的Redis命令,就可以将数据库恢复至停机之前的状态。

1.2.2 AOF配置

我们在redis.conf配置文件可以找出APPEND ONLY MODE配置项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
############################## 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 # no为不开启AOF持久化,yes为开启AOF,默认不开启

# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof" # AOF持久化文件

# 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. 不fsyn,更快
# always: fsync after every write to the append only log. Slow, Safest. 每次fsync,慢点但是最安全
# everysec: fsync only one time every second. Compromise. 每秒每次fsync,折中
#
# 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".
# 设置AOF文件的冲洗频率,一般不会直接把数据write磁盘里,而是先写入入buffer里,等待时机最后flush到磁盘
# appendfsync always # 每执行一个写命令,就对AOF文件执行一次冲洗操作,最多丢失一个命令,性能极慢,安全性极高
appendfsync everysec # 每隔1秒,就对AOF文件执行一次冲洗操作,最多丢失1秒之内产生的命令,安全性和性能折中
# appendfsync no # 不主动对AOF文件执行冲洗操作,由操作系统决定何时对AOF进行冲洗,性能最快,安全性最低

# When the AOF fsync policy is set to always or everysec, and a background
# saving process (a background save or AOF log background rewriting) is
# performing a lot of I/O against the disk, in some Linux configurations
# Redis may block too long on the fsync() call. Note that there is no fix for
# this currently, as even performing fsync in a different thread will block
# our synchronous write(2) call.
#
# In order to mitigate this problem it's possible to use the following option
# that will prevent fsync() from being called in the main process while a
# BGSAVE or BGREWRITEAOF is in progress.
#
# This means that while another child is saving, the durability of Redis is
# the same as "appendfsync none". In practical terms, this means that it is
# possible to lose up to 30 seconds of log in the worst scenario (with the
# default Linux settings).
#
# If you have latency problems turn this to "yes". Otherwise leave it as
# "no" that is the safest pick from the point of view of durability.

no-appendfsync-on-rewrite no # 最安全但会阻塞,设置为yes,则和appendfsync no类似

# Automatic rewrite of the append only file.
# Redis is able to automatically rewrite the log file implicitly calling
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
#
# This is how it works: Redis remembers the size of the AOF file after the
# latest rewrite (if no rewrite has happened since the restart, the size of
# the AOF at startup is used).
#
# This base size is compared to the current size. If the current size is
# bigger than the specified percentage, the rewrite is triggered. Also
# you need to specify a minimal size for the AOF file to be rewritten, this
# is useful to avoid rewriting the AOF file even if the percentage increase
# is reached but it is still pretty small.
#
# Specify a percentage of zero in order to disable the automatic AOF
# rewrite feature.

auto-aof-rewrite-percentage 100 # 控制触发自动AOF文件重写所需文件体积增大的比例
auto-aof-rewrite-min-size 64mb # 自动AOF重写所需要的最小文件体积

# An AOF file may be found to be truncated at the end during the Redis
# startup process, when the AOF data gets loaded back into memory.
# This may happen when the system where Redis is running
# crashes, especially when an ext4 filesystem is mounted without the
# data=ordered option (however this can't happen when Redis itself
# crashes or aborts but the operating system still works correctly).
#
# Redis can either exit with an error when this happens, or load as much
# data as possible (the default now) and start if the AOF file is found
# to be truncated at the end. The following option controls this behavior.
#
# If aof-load-truncated is set to yes, a truncated AOF file is loaded and
# the Redis server starts emitting a log to inform the user of the event.
# Otherwise if the option is set to no, the server aborts with an error
# and refuses to start. When the option is set to no, the user requires
# to fix the AOF file using the "redis-check-aof" utility before to restart
# the server.
#
# Note that if the AOF file will be found to be corrupted in the middle
# the server will still exit with an error. This option only applies when
# Redis will try to read more data from the AOF file but not enough bytes
# will be found.
aof-load-truncated yes # 加载aof时如果有错如何处理

# When rewriting the AOF file, Redis is able to use an RDB preamble in the
# AOF file for faster rewrites and recoveries. When this option is turned
# on the rewritten AOF file is composed of two different stanzas:
#
# [RDB file][AOF tail]
#
# When loading Redis recognizes that the AOF file starts with the "REDIS"
# string and loads the prefixed RDB file, and continues loading the AOF
# tail.
aof-use-rdb-preamble yes # RDB-AOF混合持久化

1.2.3 AOF持久化优缺点

优点:

  • 与RDB持久化可能会丢失大量数据相比,AOF持久化的安全性要高得多:通过使用everysec选项,用户可以将数据丢失的时间窗口限制在1s之内。
  • AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
  • AOF日志文件即使过大的时候,出现后台重写操作,也不回影响客户端的读写。因为再rewrite log的时候,会对其中的指令进行压缩,创建一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换老日志文件即可。
  • AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如不小心使用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删除,然后再将AOF文件放回去,就可以通过恢复机制,自动恢复所有数据。

缺点:

  • AOF文件存储的是协议文本,所以它的体积会比包含相同数据、二进制格式的RDB文件要大得多,并且生成AOF文件所需的时间也会比生成RDB文件所需的时间更长。
  • 因为RDB持久化可以直接通过RDB文件恢复数据库数据,而AOF持久化则需要通过执行AOF文件中保存的命令来恢复数据库(前者是直接的数据恢复操作,而后者则是间接的数据恢复操作),所以RDB持久化的数据恢复速度将比AOF持久化的数据恢复速度快得多,并且数据库体积越大,这两者之间的差距就会越明显。
  • 因为AOF重写使用的BGREWRITEAOF命令与RDB持久化使用的BGSAVE命令一样都需要创建子进程,所以在数据库体积较大的情况下,进行AOF文件重写将占用大量资源,并导致服务器被短暂地阻塞。

1.3 RDB-AOF混合持久化

1.3.1 RDB-AOF混合持久化简述

Redis从4.0版本开始引入RDB-AOF混合持久化模式,这种模式是基于AOF持久化模式构建而来的。

如果用户打开了服务器的AOF持久化功能,并且将aof-use-rdb-preamble选项的值设置成了yes,那么Redis服务器在执行AOF重写操作时,就会像执行BGSAVE命令那样,根据数据库当前的状态生成出相应的RDB数据,并将这些数据写入新建的AOF文件中,至于那些在AOF重写开始之后执行的Redis命令,则会继续以协议文本的方式追加到新AOF文件的末尾,即已有的RDB数据的后面。

换句话说,在开启了RDB-AOF混合持久化功能之后,服务器生成的AOF文件将由两个部分组成,其中位于AOF文件开头的是RDB格式的数据,而跟在RDB数据后面的则是AOF格式的数据。

1.3.2 RDB和AOF同时开启问题

如果用户使用的是Redis 4.0之前的版本,那么同时使用RDB持久化和AOF持久化仍然是可行的,只要注意以下问题即可:

  • 同时使用两种持久化功能需要耗费大量系统资源,系统的硬件必须能够支撑运行这两种功能所需的资源消耗,否则会给系统性能带来影响。
  • Redis服务器在启动时,会优先使用AOF文件进行数据恢复,只有在没有检测到AOF文件时,才会考虑寻找并使用RDB文件进行数据恢复。
  • 当Redis服务器正在后台生成新的RDB文件时,如果有用户向服务器发送BGREWRITEAOF命令,或者配置选项中设置的AOF重写条件被满足了,那么服务器将把AOF重写操作推延到RDB文件创建完毕之后再执行,以此来避免两种持久化操作同时执行并争抢系统资源。
  • 同样,当服务器正在执行BGREWRITEAOF命令时,用户发送或者被触发的BGSAVE命令也会推延到BGREWRITEAOF命令执行完毕之后再执行。

2. 参考资料

支付宝打赏 微信打赏

请作者喝杯咖啡吧