Redis作为一款被广泛应用的内存数据库,想必大家都用过,而作为内存数据库,其持久化机制是确保数据安全和稳定性的关键所在。
Redis持久化方式
Redis持久化有两种方式:RDB(Redis DataBase)和AOF(Append Only File)。
RDB:RDB文件是一个经过压缩的二进制文件。
AOF:AOF则是以追加的方式记录Redis执行的每一条写命令。
RDB 和 AOF 是可以同时开启的,在这种情况下,当Redis重启的时候会优先载入 AOF 文件来恢复原始的数据。
RDB
RDB 是 Redis 默认的持久化方式(AOF默认是关闭的),它将 Redis 在内存中的数据写入到硬盘中,生成一个快照文件。
快照文件是一个二进制文件,包含了 Redis 在某个时间点内的所有数据。
RDB的优点是快速、简单,适用于大规模数据备份和恢复。
但是,RDB也有缺点,例如数据可能会丢失,因为 Redis 只会在指定的时间点生成快照文件。如果在快照文件生成之后,但在下一次快照文件生成之前服务器宕机,那么这期间的数据就会丢失。
如下图,T2 时刻如果服务器宕机,则 k3 和 k4 键的数据可能会丢失。
由于 RDB 文件是以二进制格式保存的,因此它非常紧凑,并且在 Redis 重启时可以迅速地加载数据。相比于AOF,RDB文件一般会更小。
RDB 持久化触发有两种方式:自动 和 手动。
手动:手动方式通过 save
命令或 bgsave
命令进行。
自动:自动方式则是在配置文件中设置,满足条件时自动触发。
手动方式
- save:save 命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求。
- bgsave:bgsave 命令会 fork 一个子进程(注意是子进程,不是子线程)在后台生成快照文件,不会阻塞 Redis 服务器,服务器进程(父进程)可以继续处理命令请求。
bgsave命令执行期间,客户端发送的 save 和 bgsave 命令会被拒绝,这样的目的是为了防止父进程和子进程之间产生竞争。
自动方式
自动方式是指通过服务器配置文件的 save 选项,来让 Redis 每隔一段时间自动执行 bgsave ,本质上还是通过 bgsave 命令去实现。
配置文件的 save 选项允许配置多个条件,只要其中任何一个条件满足,就会触发 bgsave。
即:"N 秒内数据集至少有 M 个改动" 这一条件被满足时。
举个例子,如果我们向服务器提供以下配置:
save 900 1
save 300 10
save 60 10000
那么只要满足以下三个条件中的任意一个,bgsave 命令就会被执行:
- 服务器在 900秒 之内,对数据库进行了至少 1次 修改。
- 服务器在 300秒 之内,对数据库进行了至少 10次 修改。
- 服务器在 60秒 之内,对数据库进行了至少 10000次 修改。
如果用户没有主动设置 save 选项,那么服务器会为 save 选项设置默认条件:
save 900 1
save 300 10
save 60 0000
AOF
AOF 持久化是按照 Redis 的写命令顺序将写命令追加到磁盘文件的末尾,是一种基于日志的持久化方式,它保存了 Redis 服务器所有写入操作的日志记录。
AOF 的核心思想是将 Redis 服务器执行的所有写命令追加到一个文件中。当Redis服务器重新启动时,可以通过重新执行 AOF 中的命令来恢复服务器的状态。
AOF 文件解读
一个简单的 AOF 文件示例如下:
这个文件展示了两条命令:
select 0
set k1 hello
其中:
- *号:表示参数个数,后面紧跟着参数的长度和值。
- $号:表示参数长度,后面紧跟着参数的值。
AOF文件中保存的所有命令都遵循相同的格式,即以*开头表示参数个数,$开头表示参数长度,其后紧跟着参数的值。
AOF有个比较好的优势是可以恢复误操作
举个例子,如果你不小心执行了 FLUSHALL
命令,导致数据被误删了 ,但只要 AOF 文件未被重写,那么只要停止服务器,移除 AOF 文件末尾的 FLUSHALL
命令,并重启 Redis ,就可以将数据集恢复到 FLUSHALL
执行之前的状态。
AOF 的写入与同步
当启用 AOF 时,Redis 发生写命令时其实并不是直接写入到AOF 文件,而是将写命令追加到AOF缓冲区的末尾,之后 AOF缓存区再同步至 AOF文件中。
这行为其实不难理解,Redis 写入命令十分频繁,而 AOF 文件又位于磁盘上,如果每次发生写命令就要操作一次磁盘,性能就会大打折扣。
而 AOF 缓存区同步至 AOF 文件,这一过程由名为 flushAppendonlyFile
的函数完成。
而 flushAppendOnlyFile
函数的行为由服务器配置文件的 appendfsync
选项来决定,该参数有以下三个选项:
- always:每次发生写命令,都同步到 AOF 文件,是最安全的选项。
- everysec:每秒钟同步写入一次到 AOF 文件,在性能和安全之间做了一个平衡。
- no:不主动写入 AOF 文件,何时同步由操作系统来决定。
默认情况下,Redis的 appendfsync
参数为 everysec
。如果需要提高持久化安全性,可以将其改为 always
,如果更关注性能,则可以将其改为 no
。但是需要注意的是,使用 no
可能会导致数据丢失的风险,建议在应用场景允许的情况下谨慎使用。
AOF 重写
上面我们讲了 AOF 是通过追加命令的方式去记录数据库状态的,那么当随着服务器运行时间的流逝,AOF 文件可能会越来越大,达到几G甚至几十个G。
过大的 AOF 文件会对 Redis 服务器甚至宿主机造成影响,并且 AOF 越大,使用 AOF 来进行数据恢复所需的时间也就越多。
为了解决 AOF 文件体积膨胀的问题,Redis 提供了 AOF 文件重写(rewrite)机制。
Redis的 AOF 重写机制指的是将 AOF 文件中的冗余命令删除,以减小 AOF 文件的大小并提高读写性能的过程。
通过该功能,Redis 服务器可以创建一个新的 AOF 文件来替代现有的 AOF 文件,新旧两个 AOF 文件所保存的数据库状态相同,但新 AOF 文件不会包含任何浪费空间的冗余命令,所以新 AOF 文件的体积通常会比旧 AOF 文件的体积要小得多。
AOF重写的实现
虽然叫做AOF重写,但实际上,AOF 文件重写并不需要对现有的AOF 文件进行任何读取、分析或者写入操作。
AOF重写是通过读取服务器当前的数据库状态来实现的。
我举个例子大家就明白了,假设我对 Redis 执行了下面六条命令:
rpush list "A"
rpush list "B"
rpush list "C"
rpush list "D"
rpush list "E"
rpush list "F"
那么服务器为了保存当前 list键 的状态,会在AOF文件中写入上述六条命令。
而我现在要对 AOF 进行重写的话,其实最高效最简单的方式不是挨个读取和分析现有AOF文件中的这六条命令。
而是直接从数据库中读取键 list 的值,然后用一条命令:rpush list "A" "B" "C" "D" "E" "F"
,可以直接代替原 AOF 文件中的六条命令。
命令由六条减少为一条,重写的目的就达到了。
Redis的AOF重写机制采用了类似于复制的方式,首先将内存中的数据快照保存到一个临时文件中,然后遍历这个临时文件,只保留最终状态的命令,生成新的AOF文件。
具体来说,Redis执行AOF重写可以分为以下几个步骤:
- 开始AOF重写过程,向客户端返回一个提示信息。
- 创建一个临时文件,并将当前数据库中的键值对写入到临时文件中。
- 在创建的临时文件中将所有的写命令都转换成Redis内部的表示格式,即使用一系列的Redis命令来表示一个操作,例如使用SET命令来表示对某个键进行赋值操作。
- 对临时文件进行压缩,去掉多余的空格和换行符等,减小文件体积。
- 将压缩后的内容写入到新的AOF文件中。
- 停止写入命令到旧的AOF文件,并将新的AOF文件的文件名替换为旧的AOF文件的文件名。
- 结束AOF重写过程,并向客户端发送完成提示信息。
Redis提供了手动触发AOF重写的命令 BGREWRITEAOF,重写过程是由父进程 fork 出来的子进程来完成的,期间父进程可以继续处理请求。
可以在Redis的客户端中执行该命令来启动AOF重写过程。Redis 2.2 需要自己手动执行 BGREWRITEAOF
命令,到了 Redis 2.4 则可以自动触发 AOF 重写。
需要注意的是,即使手动触发AOF重写,Redis也会在满足一定条件时自动触发AOF重写,以保证AOF文件的大小和性能。
重写规则通过配置中的 auto-aof-rewrite-percentage
和 auto-aof-rewrite-min-size
选项控制。
AOF 重写面临的问题
子进程在AOF重写期间,父进程还是在继续接收和处理命令的。
那么就存在一个问题:新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的AOF文件所保存的数据库状态不一致。
如上图,T1 时刻重写前数据库存储的键只有 k1和k2,T2时刻发生重写,在T3时刻重写期间,客户端新写入了两个键:k3和k4。T4时刻重写结束。
可以观察到,T4时刻重写后的AOF文件和服务器当前的数据库状态并不一致,新的AOF文件只保存了k1和k2的两个键的数据,而服务器数据库现在却有k1、k2、k3、k4 四个键。
AOF重写缓存区
为了解决这种数据不一致问题,Redis服务器设置了一个AOF重写缓冲区。
AOF重写缓存区在AOF重写时开始启用,当Redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区。
示意图如下:
由了AOF重写缓存区的存在,当子进程完成AOF重写工作之后,它会向父进程发送一个信号,父进程在接到该信号之后,会调用处理函数,将AOF重写缓冲区中的所有内容写入到新AOF文件中(就是重写后的文件),这样重写后数据库状态就和服务器当前的数据库状态一致了。
这里有个注意的点,AOF重写缓存区同步至AOF文件中(上述红色箭头),这个过程是同步的,会阻塞父进程,在其他时候,AOF后台重写都不会阻塞父进程。
然后会对新的AOF文件进行改名,覆盖现有的AOF文件,至此完成新旧两个AOF文件的替换。
AOF重写期间,命令会同步至AOF缓存区和AOF重写缓冲区,那么可不可以使用AOF缓存区代替AOF重写缓冲区呢?
思考:AOF缓冲区可以替代AOF重写缓冲区吗?
先说结论:AOF缓冲区不可以替代AOF重写缓冲区。
原因是AOF重写缓冲区记录的是从重写开始后的所有需要重写的命令,而AOF缓冲区可能只记录了部分的命令(如果写回的话,AOF缓存区的数据就会失效被丢失,因而只会保存一部分的命令,而AOF重写缓存区不会)。
AOF 文件修复
服务器可能在程序正在对 AOF 文件进行写入时停机,造成 AOF 文件损坏。
发生这种情况时,可以使用 Redis 自带的 redis-check-aof 程序,对 AOF 文件进行修复,命令如下:
$ redis-check-aof –fix
AOF 写后日志
我们比较熟悉的是数据库的写前日志(Write Ahead Log,WAL),也就是说,在实际写数据前,先把修改的数据记到日志文件中,以便故障时进行恢复。
比如 MySQL Innodb 存储引擎中的 redo log(重做日志)便是采用写前日志。
不过,AOF 日志却正好相反,它是写后日志,“写后”的意思是 Redis 是先执行命令,把数据写入内存,然后才记录日志。
思考:为什么要这样设计?
其实为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。
而写后日志这种方式,就是先让系统执行命令,只有命令能执行成功,才会被记录到日志中,否则,系统就会直接向客户端报错。所以,Redis 使用写后日志这一方式的一大好处是,可以避免出现记录错误命令的情况,省下了语法检查的性能开销。
除此之外,AOF 写后日志还有一个好处:它是在命令执行后才记录日志,所以并不会阻塞当前的写操作。
混合持久化
在过去, Redis 用户通常会因为 RDB 持久化和 AOF 持久化之间不同的优缺点而陷入两难的选择当中:
- RDB 持久化能够快速地储存和恢复数据,但是在服务器停机时可能会丢失大量数据。
- AOF 持久化能够有效地提高数据的安全性,但是在储存和恢复数据方面却要耗费大量的时间。
为了让用户能够同时拥有上述两种持久化的优点, Redis 4.0 推出了一个“鱼和熊掌兼得”的持久化方案 —— RDB-AOF 混合持久化。
这种持久化能够通过 AOF 重写操作创建出一个同时包含 RDB 数据和 AOF 数据的 AOF 文件, 其中 RDB 数据位于 AOF 文件的开头, 它们储存了服务器开始执行重写操作时的数据库状态。至于那些在重写操作执行之后执行的 Redis 命令, 则会继续以 AOF 格式追加到 AOF 文件的末尾, 也即是 RDB 数据之后。
也就是说当开启混合持久化之后,AOF文件中的内容:前半部分是二进制的RDB内容,后面跟着AOF增加的数据。
在目前版本中, RDB-AOF 混合持久化功能默认是处于关闭状态的, 要启用该功能, 用户不仅需要开启 AOF 持久化功能, 还需要将 aof-use-rdb-preamble
选项的值设置为 true。
appendonly yes
aof-use-rdb-preamble yes
合适的持久化方式
当你想选择适合你的应用程序的持久化方式时,你需要考虑以下两个因素:
- 数据的实时性和一致性:如果对数据的实时性和一致性有很高的要求,则AOF可能是更好的选择。
如果对数据的实时性和一致性要求不太高,并且希望能快速地加载数据并减少磁盘空间的使用,那么RDB可能更适合你的应用程序。因为RDB文件是二进制格式的,结构非常紧凑,所以在Redis重启时可以迅速地加载数据。 - Redis的性能需求:如果对Redis的性能有很高的要求,那么关闭持久化功能也是一个选择。因为持久化功能可能会影响Redis的性能,但是一般不建议这么做。
总结
本篇文章到这就结束了,最后我们来做个小总结:
我们要意识到Redis的持久化机制扮演着至关重要的角色。RDB和AOF两种主要的持久化方式各有其优势和使用场景。
RDB通过提供特定时间点的数据快照,对于灾难恢复是非常有效的;而AOF则通过记录每个写入操作,提供了更好的数据持久性保证。然而,它们也有各自的局限性,这就需要根据实际需求来权衡选用哪种持久化方式。
最后,不可忽视的是,在选择合适的持久化策略时,我们还应考虑如何平衡内存使用、磁盘使用、性能与持久性等多个因素。只有对Redis持久化的深入理解,我们才能充分利用其强大的功能,以满足各种业务需求。