什么是持久化
Redis 是内存数据库,会将数据库状态存储在内存里面,一旦服务器进程退出,服务器中的数据库状态也会消失,造成数据丢失。
我们需要将内存中的数据进行持久的存储,比如:写到磁盘、硬盘,避免数据的意外丢失。
为什么持久化
-
为了重用数据
比如:机器重启、机器故障 之后需要恢复数据。
-
为了做数据同步
比如:Redis 集群的主从节点通过 RDB 文件同步数据。
持久化方式有哪些
- RDB:快照
- AOF:追加文件
- RDB + AOF 混合使用
RDB 快照
什么是 RDB 持久化?
-
通过创建快照的方法,获取某个时间点上的内存数据的副本,
- 可将该快照副本进行备份,复制到其他服务器从而创建具有相同数据的服务器副本。
- 可将该快照保存在本地,以便重启服务器后使用。
-
RDB 文件是一个经过压缩的二进制文件,可以通过该文件还原成 RDB 文件时的数据库状态。
-
RDB 快照持久化,是 Redis 默认采用的持久化方式。
RDB 文件的创建和载入
创建
-
Redis 提供了两个命令来生成 RDB 快照文件:
save:会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止(在服务器阻塞期间,不能处理任何命令的执行)才会重新开始接受命令请求。bgsave:不会阻塞 Redis 服务器进程,会派生出子进程,由该子进程创建 RDB 文件,而主进程则可以继续处理任何命令请求。
-
创建 RDB 文件是由
rdb.c / rdbSave函数完成的,两个命令会以不同的方式去调用该函数。
载入
-
RDB 文件是在服务器启动时自动载入的,没有专门的命令,只要服务器启动时检测到有 RDB 文件,就会自动载入。
❕ 注意:
服务器默认使用 RDB 文件来还原数据库状态的,如果服务器开启了 AOF 持久化能,那么就会优先使用 AOF 文件来还原数据库状态(这是因为 AOF 文件的更新频率通常比 RDB 文件的更新频率高)。
-
载入 RDB 文件是由
rdb.c /rdbLoad函数完成的。 -
载入期间,服务器会处于阻塞状态,直到载入完成。
save、bgsave、BGREWRITEAOF 命令
- SAVE 命令:在主进程上保存数据到磁盘上的一个快照文件中,执行期间,会阻塞 Redis 服务器进程,直到快照文件生成完毕。
- BGSAVE 命令:在子进程上保存数据到磁盘上的一个快照文件中。执行期间,不会阻塞 Redis 服务器的进程,因此在执行期间可以继续处理其他命令请求。
- BGREWRITEAOF 命令:在后台异步重写 Redis 数据库的 AOF(Append-Only File)日志文件,将 Redis 的写操作追加到日志文件中,也不会阻塞 Redis 服务器的进程,因此在执行期间可以继续处理其他命令请求。
❕ 注意:
BGSAVE 命令在执行期间:
- 会拒接 SAVE 命令的请求,为了避免 父进程 和 子进程 同时执行两个
rdb.c / rdbSave函数的调用,以防产生竞争条件。- 会拒接 BGSAVE 命令的请求,同时执行也会产生竞争条件。
- 会延迟 BGREWRITEAOF 命令的请求,直到 BGSAVE 命令执行结束;相反 BGREWRITEAOF 命令执行期间,会拒接 BGSAVE 命令(主要是性能方面的考虑)。
BGSAVE 命令自动间隔保存
因为 BGSAVE 命令是在子进程上执行,不会阻塞服务器父进程,所以我们可以通过设置服务器配置的 save 选项,让服务器每个一段时间就自动执行一次 BGSAVE 命令。
在 redis.conf 配置文件中默认有此下配置:
save 900 1 #在 900秒(15分钟)之内,至少对数据库修改了 1 次,Redis 则自动触发 BGSAVE 命令创建快照。
save 300 10 #在 300秒(5分钟)之内,至少对数据库修改了 10 次,Redis 则自动触发 BGSAVE 命令创建快照。
save 60 10000 #在 60秒(1分钟)之内,至少对数据库修改了 10000 次,Redis 则自动触发 BGSAVE 命令创建快照。
AOF 追加文件
什么是 AOF 持久化?
与 RDB 快照持久化相比,AOF 持久化的实时性更好。
AOF 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态。服务器在启动时,通过载入和执行 AOF 文件中保存的命令来还原服务器关闭之前的数据库状态。
默认情况下 Redis 没有开启 AOF 持久化(Redis 6.0 之后已经默认是开启了),可以通过 appendonly 参数手动开启:
appendonly yes
AOF 持久化的实现
分为三个步骤:追加(append)、文件写入、文件同步(sync)
命令追加
AOF 持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议的格式将该命令追加到服务器状态的 aof_buf 缓存区的末尾(AOF 缓冲区)。
文件写入 与 文件同步
服务器每次结束一个事件循环之前,都会调用 flushAppendOnlyFilel 函数,考虑是否需要将 aof_buf 缓冲区中的内容写入/保存到 AOF 文件里。
Redis 的服务器进程就是一个事件循环 (lop),这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像 servercron 函数这样需要定时运行的函数。
flushAppendOnlyFilel 函数的行为是由服务器配置的 appendfsync 选项的值来决定的:
| appendfsync 选项的值 | flushAppendOnlyFilel 函数的行为 |
|---|---|
| always | 在每次写入命令到 AOF 缓冲区后,Redis 会立即将 AOF 缓冲区的内容同步到 AOF 文件,并使用 fsync 命令强制将数据同步到硬盘上。 |
| everysec(默认) | Redis 会启动一个定时任务,每隔一秒钟将 AOF 缓冲区的内容同步到 AOF 文件,并使用 fsync 命令强制将数据同步到硬盘上。 |
| no | Redis 不会主动将 AOF 缓冲区的内容同步到 AOF 文件,而是依赖于操作系统的异步写入。这意味着可能存在一定的数据丢失风险。 |
另外,当 AOF 缓冲区的内容累积到一定程度时,Redis 也会将这些内容写入 AOF 文件。这个累积的阈值可以通过配置文件中的 auto-aof-rewrite-min-size 参数来设置。
AOF 文件的载入与数据还原
服务器重启后,只需要读入并重新执行一遍 AOF 文件里保存的的写命令,就可以还原服务器关闭之前的数据库状态。
AOF 校验机制
AOF 校验机制是 Redis 在启动时对 AOF 文件进行检查,以判断文件是否完整,是否有损坏或者丢失的数据。这个机制的原理其实非常简单,就是通过使用一种叫做 校验和(checksum) 的数字来验证 AOF 文件。这个校验和是通过对整个 AOF 文件内容进行 CRC64 算法计算得出的数字。如果文件内容发生了变化,那么校验和也会随之改变。因此,Redis 在启动时会比较计算出的校验和与文件末尾保存的校验和(计算的时候会把最后一行保存校验和的内容给忽略点),从而判断 AOF 文件是否完整。如果发现文件有问题,Redis 就会拒绝启动并提供相应的错误信息。AOF 校验机制十分简单有效,可以提高 Redis 数据的可靠性
AOF 重写
-
随着时间,AOF 文件的内容会越来越多,体积也越来越大,如果不进行控制,对会对 Redis 服务器、计算机造成影响,且还原数据所需的时间就越多。
-
所以,Redis 提供了 AOF 重写功能,也就是 Redis 服务器会创建一个新的 AOF 文件来替代现有的 AOF 文件,新旧文件所保存的数据库状态相同,但是新文件会去除任何浪费空间的冗余命令,新文件会比旧文件的体积更小。
-
实际上,AOF 文件重写(新)并不会对现有的 AOF 文件(旧)进行任何读取、分析或写入操作,而是通过读取服务器当前的数据库状态来实现的。
例如:
redis> RPUSH list "A" "B" //["A","B"] (integer) 2 redis> RPUSH list "C" //["A","B","C"] (integer) 3 redis> RPUSH list "D" "E" //["A","B","C","D","E"] (integer) 5 redis> LPOP list "A" //["B","C","D","E"] "A" redis> LPOP list "B" //["C","D","E"] "B"如果服务器要保存尽量少的命令,不是直接保存这 5 条命令,而是直接从数据库中读取键 list 的值,然后用 1 条命令来代替保存在 AOF 文件中的这 5 条命令
redis> RPUSH list "C" "D" "E" //["C","D","E"] (integer) 3 -
开启 AOF 重写功能:
-
调用
BGREWRITEAOF命令手动执行 -
设置下面两个配置项,让程序自动决定触发时机:
auto-aof-rewrite-min-size:如果 AOF 文件大小小于该值,则不会触发 AOF 重写。默认值为 64 MB;
-
auto-aof-rewrite-percentage:执行 AOF 重写时,当前 AOF 大小(aof_current_size)和上一次重写时 AOF 大小(aof_base_size)的比值。如果当前 AOF 文件大小增加了这个百分比值,将触发 AOF 重写。将此值设置为 0 将禁用自动 AOF 重写。默认值为 100。
-
-
AOF 重写由
aof_rewrite函数产生新的 AOF 文件,并且 Redis 服务器会将 AOF 重写功能由子进程执行,父进程可以继续处理请求命令(是子进程不是子线程,所以可以保存数据的安全性)aof_rewrite函数生成的新 AOF 文件只包含还原当前数据库状态所必须的命令,所以新 AOF 文件不会浪费任何硬盘空间。 -
子进程执行 AOF 重写期间,如果父进程有新的写命令,那么就会出现重写后的文件与当前数据库状态不一致。
为了解决这个问题,Redis 服务器设置了一个 AOF 重写缓冲区:这个重写缓冲区会在创建子进程后开始使用,当父进程(Reids 服务器)执行完一个写命令,同时会将这个命令发送给 AOF 缓冲区和 AOF 重写缓存区。
-
当子进程的 AOF 重写工作完成之后,向父进程发送信号,父进程接受到信号后调用信号处理函数,并执行以下工作:
-
将 AOF 重写缓冲区中的内容写入到 AOF 新文件,保持新 AOF 文件与当前数据库状态一致。
-
对新的 AOF 文件重命名,覆盖现有的 AOF 文件,完成替换。
这个信号处理函数执行期间会对父进程造成阻塞,只有执行完毕后,才可以继续接受命令请求。
-
AOF 记录日志
-
关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。
1)Redis 执行命令写内存 2)记录日志,写磁盘
-
为什么是在执行完命令之后记录日志呢?
- 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
- 在命令执行完之后再记录,不会阻塞当前的命令执行。
-
带来的风险
- 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
- 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。
RDB 和 AOF 的混合持久化
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。
如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
RDB 和 AOF 对比
RDB 比 AOF 优秀的地方:
-
RDB 文件存储的内容是经过压缩的二进制数据, 保存着某个时间点的数据集,文件很小,适合做数据的备份,灾难恢复。
AOF 文件存储的是每一次写命令,类似于 MySQL 的 binlog 日志,通常会比 RDB 文件大很多。当 AOF 变得太大时,Redis 能够在后台自动重写 AOF。新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。不过, Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。
-
使用 RDB 文件恢复数据,直接解析还原数据即可,不需要一条一条地执行命令,速度非常快。
而 AOF 则需要依次执行每个写命令,速度非常慢。也就是说,与 AOF 相比,恢复大数据集的时候,RDB 速度更快。
AOF 比 RDB 优秀的地方:
-
RDB 的数据安全性不如 AOF,没有办法实时或者秒级持久化数据。生成 RDB 文件的过程是比较繁重的, 虽然 BGSAVE 子进程写入 RDB 文件的工作不会阻塞主线程,但会对机器的 CPU 资源和内存资源产生影响,严重的情况下甚至会直接把 Redis 服务干宕机。
AOF 支持秒级数据丢失(取决 fsync 策略,如果是 everysec,最多丢失 1 秒的数据),仅仅是追加命令到 AOF 文件,操作轻量。
-
RDB 文件是以特定的二进制格式保存的,并且在 Redis 版本演进中有多个版本的 RDB,所以存在老版本的 Redis 服务不兼容新版本的 RDB 格式的问题。
-
AOF 以一种易于理解和解析的格式包含所有操作的日志。可以轻松地导出 AOF 文件进行分析,也可以直接操作 AOF 文件来解决一些问题。比如,如果执行
FLUSHALL命令意外地刷新了所有内容后,只要 AOF 文件没有被重写,删除最新命令并重启即可恢复之前的状态。
总结:
- Redis 保存的数据丢失一些也没什么影响的话,可以选择使用 RDB。
- 不建议单独使用 AOF,因为时不时地创建一个 RDB 快照可以进行数据库备份、更快的重启以及解决 AOF 引擎错误。
- 如果保存的数据要求安全性比较高的话,建议同时开启 RDB 和 AOF 持久化或者开启 RDB 和 AOF 混合持久化。