Redis的持久化机制

1.1 Redis持久化方式

Redis 中提供了 RDBAOF 两种持久化方式

  • RDB 记录的是数据,对应的文件内容是二进制, 如果生产环境允许分钟级别的数据丢失,可以只使用 RDB
  • AOF 记录的是操作,如果只使用 AOF,则优先使用 everysec 配置(下面会介绍)
  • 若数据不能丢失时,RDBAOF 的混合使用是一个很好的选择

1.2 RDB

  1. RDB 持久化就像是给 Redis 的整个内存做快照,然后将这个快照持久化到一个 .rdb 文件中
  2. Redis 在重启时,可以通过加载 RDB 文件快速恢复 Redis 内存数据
  3. Redis 会丢失最后一次 RDB 文件到重启之间的数据

1.2.1 如何触发 RDB

1.2.1.1 手动触发 RDB

  • 手动执行 SAVE 会使用 Redis 主线程 进行 RDB 持久化, 需要注意的是这段时间会造成整个 Redis 不可用,因此在生产环境不可以使用 SAVE 命令
  • 手动执行 BGSAVEfork 出一个子进程 来进行 RDB 持久化,不会阻塞主进程, 需要注意的是不阻塞主进程指的是子进程中的操作不会阻塞主进程,但是 fork 这个动作本身是会阻塞主进程的,因为 fork 子进程的操作就是在主进程中完成的

1.2.1.2 自动触发 RDB

控制自动触发的参数是 redis.conf 中如下配置, 下面配置是拷贝的 Redis configuration file example | Redis 中关于 RDB 配置相关的内容

conf 复制代码
# Save the DB to disk.
#
# save <seconds> <changes> [<seconds> <changes> ...]
#
# Redis will save the DB if the given number of seconds elapsed and it
# surpassed the given number of write operations against the DB.
#
# Snapshotting can be completely disabled with a single empty string argument
# as in following example:
#
# save ""
#
# Unless specified otherwise, by default Redis will save the DB:
#   * After 3600 seconds (an hour) if at least 1 change was performed
#   * After 300 seconds (5 minutes) if at least 100 changes were performed
#   * After 60 seconds if at least 10000 changes were performed
#
# You can set these explicitly by uncommenting the following line.
#
# save 3600 1 300 100 60 10000
  • save 配置的作用是保存数据到磁盘 【在 RedisServer 中执行的是 bgsave 命令】
  • save 配置的格式是 save <seconds> <changes> [<seconds> <changes> ...] 当然也可以写成多行的形式
  • save "" 的意思是禁用 rdb 功能
1.2.1.2.1 RDB配置示例
conf 复制代码
save 3600 1
save 300 10
save 60 10000

上述配置的含义是只要满足了三个条件中的任何一个,就会执行 bgsave 命令(这些条件是 的关系 )

  • 在 3600s 内(1 小时),对数据库至少进行了 1 次修改
  • 在 300s 内(5 分钟),对数据库至少进行了 10 次修改
  • 在 60s 内(1 分钟),对数据库至少进行了 10000 次修改 这里的时间(1分钟,5分钟,1小时)代表的其实是 距离上次发生 RDB 的时间

1.2.2 动态设置 RDB 配置

前面讲的 save 配置是写在 redis.conf 中的,如果已经启动了 redis,则可以通过如下命令来动态修改 save 配置

sql 复制代码
CONFIG SET SAVE "3600 3 300 10 60 10000"

1.2.3 RDB 之 copy-on-write 技术

前面讲到使用 bgsave 命令时会从主进程 fork 出一个子进程然后将 db 快照存在 RDB 文件中,这里有几个问题可以思考一下

  1. fork 出的子进程是将父进程的内存完全拷贝了一份么?
  2. 子进程在存储快照数据的时候,redis 还可以进行数据的修改操作么?如果可以那主进程和子进程数据不同了怎么办? Redis 使用 copy-on-write 技术来解决上面两个问题,对于第一个问题,子进程肯定不适合将父进程的内存完全拷贝一份,因为当数据量很大的时候,如果完全拷贝一份内存就要翻倍,所以不合适,也就是说子进程一开始肯定是共用父进程的内存空间的,共用的话带来第二个问题,首先在子进程在存储快照期间,redis 肯定是要能响应修改操作的,但是父子进程共用空间,如果父进程中执行了 redis 的修改操作,子进程数据也变了,这就不合理了,所以 redis 使用 copy-on-write 技术来解决,即子进程 RDB 还未完成且父进程接收到了修改命令,则会将 key 所在内存的 内存页 进行拷贝,在拷贝后的内存中修改,待子进程完成 RDB 后再合并数据

1.2.3.1 为什么要使用 copy-on-write

前面提到子进程在执行 RDB 时,若父进程修改了内存数据,则会导致子进程内存数据一起修改了,这样不合理,但是没有具体说为什么不合理,如果对 Java 并发有了解的话可以知道,一个线程对共享变量只是读,另外线程对共享变量进行读写不会有并发问题,为什么在 Redis 进行 RDB 时就一定要保证子进程的内存数据和 fork 时内存数据一致呢?即使数据被修改了,那保存在 RDB 中的数据刚好是最新的,应该更不会有问题才对?

要理解上面这个问题,不能单纯从 RDB 来看,因为 Redis 中还有一种持久化方式是 AOF,如果单纯使用 RDB 的话上面那种场景确实没有问题,不过单纯使用 RDB 会存在丢失数据的风险,所以要结合 AOF 来使用,此时就会出问题了,比如看下面场景

shell 复制代码
1. 子进程开始进行 RDB,此时没有 "xxx" 这个 key
2. 主进程收到命令 incr("xxx"), 此时 xxx 对应的值是 1 (注意此时会进行 AOF 命令保存)
3. 子进程保存 RDB 文件时保存了 xxx 这个 key 的结果

经过上面的场景后,RDB 文件中存在 key=xxx, value=1 的结果,同时 AOF 中也保存了 incr("xxx") 的命令,那么最终数据合并的时候,DB 中会是 key=xxx, value=2 的结果,这个就是错误的,所以一定要保证 fork 出子进程之后,子进程内存中的数据都不可变才行,对于变化的那部分数据就不用 RDB 文件管了,自然会有 AOF 来处理

1.3 AOF

1.3.1 什么是 AOF

  • AOF 是增量持久化方式,每一条修改操作的命令 都会持久化到 AOF 文件,在发生故障后可以做到不丢数据或丢失较少的数据,具体那种要看 AOF 相关配置参数(下面介绍)

1.3.2 为什么要使用 AOF

  • RDB 持久化是全量持久化方式,比较耗时,所以只适合定时触发
  • 由于 RDB 是定时触发,如果 Redis 崩溃后从 RDB 文件中恢复数据,可能会造成数据丢失

1.3.3 如何开启 AOF

AOF 功能是通过 redis.conf 中如下配置开启的

conf 复制代码
# 如果配置成 no 就是不开启 aof 功能
appendonly yes

1.3.4 AOF记录时机

一般记录日志可能都是在正式操作之前先记录日志,不过 AOF 是在执行命令之后才记录的日志,这样做有优点也有缺点

1.3.4.1 优点

记录日志时不用检查命令是否正确,因为如果命令可以正确执行,那命令一定是正确的,如果命令执行失败,也根本就不用记录日志

1.3.4.2 缺点

  • 有可能命令执行成功,但是日志记录失败
  • AOF 日志虽然没有阻塞当前命令,但是可能阻塞后续命令,因为 AOF 日志也是在主线程写入 AOF缓冲区的 这两个缺点都和 AOF 日志刷盘时机有关,因此接下来看看 AOF 写日志的策略

1.3.5 AOF 写日志策略

redis.conf 中可以通过 appendfsync 配置来修改刷盘时机, 主要有如下三个值

conf 复制代码
# always, everysec, no
appendfsync no
  • always: 同步写回,每个写命令执行完都同步的将日志写回磁盘, 基本不丢数据,但是可能会影响性能
  • everysec :每个写命令执行完后先把日志写到 AOF 文件的内存缓冲区,每隔 1s 把缓冲区中的内容写入磁盘
  • no: 操作系统控制的写回, 每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘

1.3.6 AOF 重写机制

1.3.6.1 什么是 AOF 重写机制

AOF 重写机制就是 Redis 根据数据库的数据创建一个新的 AOF 文件,即读取所有键值对,然后对每一个键值对用一条命令记录它的写入,比如知道 Redis 内存中有 key=key1, value=value1 的数据,则记录一条 set key1 value1 即可, 那么为什么重写可以减小 AOF 文件大小呢? 看下面的示例

shell 复制代码
set key1 value1
del key1
set key1 value2
set key1 value3

经过上述操作后,AOF 文件中保存了四条指令,但是经过 AOF 重写后,只需要一条 set key1 value3 即可

1.3.6.2 为什么需要 AOF 重写机制

  • AOF 文件内容不断的累增,文件系统自身也无法保存太大的文件,而且往大文件中增加内容,也会影响性能
  • AOF 文件主要是用于故障恢复,如果 AOF 文件太大,故障恢复的效率会很低

1.3.7 什么时候启动 AOF 重写流程

  • 手动调用 BGREWRITEAOF 命令
  • 通过 CONFIG SET appendonly yes 命令开启时会重写
  • 触发了 Redis 配置指定的定时 Rewrite 条件,可以参考 Redis configuration file example | Redis 中关于 aof rewrite 相关的部分,下面代码就是摘录的相关参数
conf 复制代码
# 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
auto-aof-rewrite-min-size 64mb
  • auto-aof-rewrite-min-size: 表示运行 AOF 重写时文件的最小大小,默认为 64MB
  • auto-aof-rewrite-percentage: 计算方式为 (当前 AOF 文件大小 - 上一次重写后 AOF 文件大小)/ 上一次重写后 AOF 文件大小

1.3.8 Redis7之前的AOF重写流程

参考 《说透Redis7》-- 掘金小册

  1. 主进程 fork 子进程,此时子进程拥有 Redis 内存快照
  2. 子进程扫描内存中的全部键值对,生成 Rewrite 临时文件
  3. 在此期间主进程将 AOF 日志同时写入 AOF 缓冲区和 Rewrite 缓冲区,并且 AOF 缓冲区依然会正常刷盘
  4. 子进程完成 Rewrite 操作后,主进程会将 Rewrite 缓冲区数据写入临时文件,然后将临时文件重命名,替换原来的 AOF 文件
  5. 后续的 AOF 日志写入到这个 Rewrite 之后的 AOF 文件中

1.3.8.1 Redis7 之前重写流程存在的问题

  1. 新增了 AOF Rewrite 缓冲区,在写入密集情况下会消耗大量内存
  2. 因为新增了 AOF Rewrite, 所以同一份数据会存在两次 IO
    1. 一次是原来缓冲区刷盘的数据
    2. 第二次是 AOF Rewrite 缓冲区刷盘的数据
  3. Rewrite 操作结束后将 Rewrite 缓冲区刷盘操作是在主进程中完成的,若 Rewrite 缓冲区比较大,就会阻塞命令执行,影响性能 鉴于以上问题,在 Redis7 中使用 Multi-Part AOF 机制

1.3.9 Redis7 中的 AOF 重写流程

参考以下文章 Redis 7.0 Multi Part AOF的设计和实现 - 知乎 (zhihu.com)

1.3.10 AOF 相关问题

对于 expire 指令,aof 是如何处理的?

因为 expire 是指定过期时间,比如 10分钟后过期,如果在这期间,Redis 宕机了,使用 AOF 进行恢复时,若使用原来的命令,那过期时间就是错误的,所以对于 expire 指令,在记录 AOF 文件时已经进行了转换,使用 expireat 命令

对于类似 spop 有返回值的指令是否原样记录?

spop 指令作用是 Removes and returns one or more random members from the set value store at key,即随机删除并返回 Set 中给定数量的元素,但是在使用 AOF 进行故障恢复时,返回值是没有作用的,所以在进行 AOF 写入时就回替换成 srem 指令

1.4 RDB 和 AOF 的比较

1.4.1 能否只用 RDB

  1. RDB 的优势是 Redis 崩溃后加载数据快,因为 RDB 保存的是全量数据,数据恢复时只需要一条指令将就可以将数据都加载到内存中
  2. RDB 的缺点是保存数据时因为是全量保存,所以当内存数据多时,耗时比较长,也就意味着在执行 RDB 文件期间 Redis 的数据修改操作都可能会丢失(假如刚好在这期间 Redis 崩溃的话),所以生产环境不建议只使用 RDB 方式

1.4.2 能否只用 AOF

  1. AOF 的优势时数据保存比较快,只需要将修改命令不断追加到文件中,这样如果出现问题数据丢失的风险就低
  2. AOF 的缺点是数据加载相对来说比 RDB 慢,因为 AOF 中保存的是一条条的命令,所以数据加载时会需要执行很多条命令才可以将数据恢复
相关推荐
2401_857622662 小时前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
2402_857589362 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
哎呦没3 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
杨哥带你写代码5 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
AskHarries6 小时前
读《show your work》的一点感悟
后端
A尘埃6 小时前
SpringBoot的数据访问
java·spring boot·后端
yang-23076 小时前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
Marst Code6 小时前
(Django)初步使用
后端·python·django
代码之光_19806 小时前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端