Redis 学习笔记 - 持久化

上一篇文章介绍了 Redis 的基本命令操作,本文主要介绍 Redis 的另一个特性,持久化。

Redis 支持 RDB 和 AOF 两种持久化机制,对内存中的数据进行持久化,可以有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复

RDB

RDB 持久化把当前进程数据生成快照保存到文件的过程,

优缺点

RDB 持久化方式的优点:

  1. RDB 是一个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据快照。适用于备份, 例如每 24 小时生成 RDB 文件归档,并且每 30 天保存 RDB 文件快照,这样可以方便恢复不同版本的数据;
  2. RDB 适用于灾难恢复,压缩后的二进制文件便于传递至远程数据中心进行备份恢复;
  3. RDB 方式采用 fork 子进程的方式进行写入操作,对于父进程不会造成长期阻塞,因此性能较好;
  4. 加载 RDB 恢复数据远远快于 AOF 的方式。

RDB 持久化方式的缺点:

  1. RDB 方式数据没办法做到秒级持久化, 不能避免在服务器故障时丢失数据。因为 bgsave 每次运行都要执行 fork 操作创建子进程,属于重量级操作,频繁执行成本过高, 可能会至少 5 分钟才保存一次 RDB 文件。在这种情况下,一旦发生故障停机,有可能会丢失分钟的数据。
  2. 因为每次保存 RDB 时都要 fork 出一个子进程,并由子进程来进行实际的持久化工作。在数据集比较庞大时, fork 可能会比较耗时,造成服务器在短时间内停止处理客户端请求;虽然 AOF 重写也需要进行 fork, 但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失。
  3. RDB 文件使用特定二进制格式保存,Redis 版本演进过程中有多个格式的 RDB 版本,存在老版本 Redis 服务无法兼容新版 RDB 格式的问题。

触发机制

RDB 持久化触发过程可分为手动触发和自动触发。

手动触发 RDB 持久化可通过 savebgsave 命令进行:

  1. save: 阻塞当前 Redis 服务器,直到 RDB 过程完成为止,如果当前内存较大会造成长时间阻塞,线上环境不建议使用。
  2. bgsave: Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。bgsave 命令是针对 save 阻塞问题做的优化。因此目前 Redis 内部所有的涉及 RDB 的操作都采用 bgsave 的方式。

自动触发 RDB 的持久化机制通常在以下场景:

  1. 使用 save 相关配置,如 save m n 表示 m 秒内数据集存在 n 次修改时,自动触发 bgsave;
  2. 如果从节点执行全量复制操作,主节点自动执行 bgsave 生成 RDB 文件并发送给从节点;
  3. 执行 debug reload 命令重新加载 Redis 时,也会自动触发 save 操作;
  4. 默认情况下执行 shutdown 命令时,如果没有开启 AOF 持久化功能则自动执行 bgsave;

bgsave 流程

对于目前主流的 bgsave 命令,其执行流程如下:

  1. 执行 bgsave 命令,Redis 父进程判断当前是否存在正在执行的子进程(如 RDB/AOF 子进程),如果存在 bgsave 命令直接返回;
  2. 父进程执行 fork 操作创建子进程,fork 操作过程中父进程会阻塞;
  3. 父进程 fork 完成后,bgsave 命令返回 Background saving started 信息, 并不再阻塞父进程,可以继续响应其他命令;
  4. 子进程创建 RDB 文件,根据父进程内存生成临时快照文件, 完成后对原有文件进行原子替换;
  5. 子进程发送信号给父进程表示完成,父进程更新统计信息。

二、AOF

AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。AOF 的主要作用是解决了数据持久化的实时性,目前已经是 Redis 持久化的主流方式。

优缺点

AOF 持久化方式的优点:

  1. 使用 AOF 持久化会让 Redis 变得更加耐久,可以设置不同的 fsync 策略。 AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据。
  2. AOF 文件是一个只进行追加操作的日志文件,因此对 AOF 文件的写入不需要进行 seek,即使日志因为某些原因而包含了未写入完整的命令,redis-check-aof 工具也可以轻易地修复这种问题。
  3. Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写,重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
  4. AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 协议的格式保存,因此 AOF 文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。

AOF 持久化方式的缺点:

  1. 对于相同的数据集,AOF 文件的体积通常要大于 RDB 文件的体积;
  2. 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB。在一般情况下,每秒 fsync 的性能依然非常高,而关闭 fsync 可以让 AOF 的速度和 RDB 一样快,即使在高负荷之下也是如此。不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间;
  3. AOF 在过去曾经发生过这样的 bug :因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。

使用 AOF

开启 AOF 功能需要设置配置:appendonly yes,默认不开启。AOF 文件名通过 appendfilename 设置,默认文件名是 appendonly.aof,保存路径同 RDB 持久化方式一致,通过 dir 配置指定。AOF 工作流程如下:

  1. 命令写入 (append): 所有的写入命令会追加到 aof_buf (缓冲区) 中, AOF 命令写入的内容直接是文本协议格式。
  2. 文件同步 (sync): AOF 缓冲区根据对应的策略向硬盘做同步操作;
  3. 文件重写 (rewrite): 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的;
  4. 重启加载 (load): 当 Redis 服务器重启时,可以加载 AOF 文件进行数据恢复。

文件同步:

Redis 提供了三种 AOF 缓冲区同步文件策略,由参数 appendfsync 控制,可配置值为 alwaysnoeverysec

  • 配置为 always 时,命令写入 aof_buf 后调用系统 fsync 操作同步到 AOF 文件,fsync 完成后线程返回。每次写入都要同步 AOF 文件,在一般的 SATA 硬盘上,Redis 只能支持大约几百 TPS 写入,显然跟 Redis 高性能特性背道而驰,不建议配置。
  • 配置为 no 时,命令写入 aof_buf 后调用系统 write 操作, 不对 AOF 文件做 fsync 同步,同步硬盘操作由操作系统负责。由于操作系统每次同步 AOF 文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但数据安全性无法保证。
  • 配置为 everysec,命令写入 aof_buf 后调用系统 write 操作, write 完成后线程返回,fsync 同步文件操作由专门线程每秒调用一次。是建议的同步策略,也是默认配置,做到兼顾性能和数据安全性,理论上只有在系统突然宕机的情况下丢失 1 秒的数据。

重写机制:

随着命令不断写入 AOF,文件会越来越大,为了解决这个问题,Redis 引入 AOF 重写机制压缩文件体积。AOF 文件重写是把 Redis 进程内的数据转化为写命令同步到新 AOF 文件的过程。重写后的 AOF 文件变小有如下原因:

  1. 进程内已经超时的数据不再写入文件。
  2. 旧的 AOF 文件含有无效命令,重写使用进程内数据直接生成,这样新的 AOF 文件只保留最终数据的写入命令。
  3. 多条写命令可以合并为一个,为了防止单条命令过大造成客户端缓冲区溢出,对于 listsethashzset 等类型操作,以 64 个元素为界拆分为多条。

AOF 重写过程可以手动触发和自动触发:

  • 手动触发:直接调用 bgrewriteaof 命令。
  • 自动触发:根据 auto-aof-rewrite-min-sizeauto-aof-rewrite-percentage 参数确定自动触发时机。auto-aof-rewrite-min-size 表示运行 AOF 重写时文件最小体积,默认为 64MB。auto-aof-rewrite-percentage 代表当前 AOF 文件空间(aof_current_size)和上一次重写后 AOF 文件空间(aof_base_size)的比值。自动触发时机 = aof_current_size > auto-aof-rewrite-min-size &&(aof_current_size-aof_base_size)/ aof_base_size >= auto-aof-rewrite-percentage

AOF 重写流程如下:

  1. 执行 AOF 重写请求。如果当前进程正在执行 AOF 重写,请求不执行;如果当前进程正在执行 bgsave 操作,重写命令延迟到 bgsave 完成之后再执行;
  2. 父进程执行 fork 创建子进程,开销等同于 bgsave 过程;
  3. 主进程 fork 操作完成后,继续响应其他命令。所有修改命令依然写入 AOF 缓冲区并根据 appendfsync 策略同步到硬盘,保证原有 AOF 机制正确性;
  4. 由于 fork 操作运用写时复制技术,子进程只能共享 fork 操作时的内存数据。由于父进程依然响应命令,Redis 使用“AOF 重写缓冲区”保存这部分新数据,防止新 AOF 文件生成期间丢失这部分数据

重启加载:

AOF 和 RDB 文件都可以用于服务器重启时的数据恢复。Redis 持久化文件加载流程如下:

  1. AOF 持久化开启且存在 AOF 文件时,优先加载 AOF 文件;
  2. AOF 关闭或者 AOF 文件不存在时,加载 RDB 文件;
  3. 加载 AOF/RDB 文件成功后,Redis 启动成功;
  4. AOF/RDB 文件存在错误时,Redis 启动失败并打印错误信息。

RDB 和 AOF 的选择

通常情况下,如果非常关注数据安全性,应该同时使用两种持久化功能。如果可以承受数分钟以内的数据丢失,那么可以只使用 RDB 持久化。并不推荐单独使用 AOF 方式,因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份,并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用 RDB 还可以避免之前提到的 AOF 程序的 bug 。

参考资料

Redis Persistence