持久化可以避免进程退出而数据丢失。

Redis 的持久化文件主要包括两种格式,RDB 格式和 AOF 格式,这两种格式的生成机制各有不同。

Redis 在重启时优先加载 AOF 文件(因为 AOF 文件颗粒度更细),如果没有 AOF 文件可加载则加载 RDB 文件。

此处输入图片的描述

RDB

RDB 持久化会把进程内的数据全量快照保存到硬盘上,其触发方式包括手动触发和自动触发。

手动触发

1
2
3
4
5
# 阻塞当前 Redis 服务器,直到 RDB dump 完成为止。内存里数据越大,阻塞时间越久。不建议在生产环境使用,未来会被废弃。
save

# fork 出一个子进程。子进程负责生成 RDB 文件后,通知父进程,主进程还可以继续响应其他命令。只有 fork 的一瞬间会阻塞 Redis 进程。
bgsave

bgsave 的工作流程如下:

bgsave 的工作流程

注意,RDB 文件本身永远只有一个。子进程产生的都是临时文件,会通过原子替换的方式来维持 RDB 文件的唯一性。

Redis 内部主动生成 RDB 文件的过程都是采用 bgsave 的方式。

自动触发

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

RDB 文件的存储

默认会存在 config set dir {newDir}config set dbfilename {newFileName}的文件里。也可以动态修改()比如在硬盘出问题的时候动态修改)。

默认情况下会对 RDB 文件进行 LZF 压缩处理(可以关闭)。压缩会消耗 CPU 性能,但利于网络传输。

RDB 的优缺点

优点:

  • 紧凑存储,适于全量保存/恢复。
  • 加载 RDB 的速度远快于 AOF 文件。

缺点:

  • 没有办法强实时/秒级持久化。
  • 版本不兼容

AOF(append only file)

以独立的日志(log)记录每次写命令,重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。AOF 具有持久化的强实时性,是 RDB 的补充。

配置方式

需要设置appendonly yes(默认不开启),开启后就会被自动触发(也就是说无需手动触发)。可以修改文件名和路径。

工作流程

aof 的工作流程如下:

此处输入图片的描述

其中:

  1. append 其实是把写命令写入 aof_buf 中。
  2. sync 是把写入命令从内存持久化到硬盘中。
  3. rewrite 则会压缩重写 AOF 文件使其达到更紧凑的目的。
  4. 当 Redis 重启后,可以加载 AOF 文件进行数据恢复。

其中被持久化的命令,是纯文本的格式,遵循 RESP 协议。

文件同步

Redis 本身是个单线程架构,如果每次新的写命令都要写入硬盘,则硬盘的负载能力可能成为一个瓶颈。而如果先写入一个 aof_buf,则可以采取多种策略持久化数据到硬盘中。

这提醒我们,对于零散的多次写 IO,我们都要想办法化零为整,通过 buffer-sync 的分段异步写形式充分协调内存和硬盘的写性能。

我们可以采用的策略分别是:

  • always 命令写入 aof_buf 后调用系统 fsync 操作同步到 AOF 文件,fsync 完成后系统返回。等于一个强实时的策略。在 sata 上只有几百的 tps。
  • everysec 命令写入 aof_buf 后调用系统 write 操作,write 完成后线程返回。fsync 操作由专门线程每秒调用一次。等于一个中等实时的策略。默认策略,最多丢失 1s 的数据
  • no 命令写入 aof_buf后调用系统 write 操作,而不对 AOF 做 fsync 同步操作, fsync 同步操作由操作系统负责,通常同步周期最长 30 秒。无法保障同步实时性。

write 其实是一种 delaywrite,写入操作系统的页缓冲区以后线程会立即返回(阻塞期很短),依赖于系统的调度(如时间阈值满或者空间阈值满,期间如果系统宕机则数据会丢失)或者 fsync才能真正持久化到硬盘中。
fsync 就是强行同步到硬盘的syscall,阻塞时间会稍长,但持久化更彻底。

重写机制

重写主要通过有效地去除无用命令、合并多条有效命令的方式使得 AOF 文件变得更小,更利于加载。

重写(只是重写这个过程)也同样包括手动触发和被动触发两种机制。

重写流程:

此处输入图片的描述

其中同时存在两个 aof 文件,新的 aof 文件也会进行一个原子替换。

手动触发

1
bgrewrite

自动触发

if (aof_current_size > auto-aof-rewrite-min-size && (aof_current_size - aof_base_size)/aof_base_size >= auto-aof-rewrite-percentage)

auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 是配置文件的配置项。

相关问题

fork 问题

fork 调用会让子进程调用父进程的内存页表,理论上一个进程占用内存越大,它的 fork 命令就越耗时,而且有些虚拟机(如 xen)里 fork 的性能还更差。

性能优化点

cpu

  1. 不要和其他 cpu 密集型应用部署在一起。
  2. 如果部署多个 Redis 实例,保证每个时刻只有一个后台进程在工作。

内存

  1. 如果部署多个 Redis 实例,保证每个时刻只有一个后台进程在工作。
  2. 不要增加 copy-on-write 的负担,尽可能让子进程少一些后台重写的负担(如何做到?)。
  3. 关掉大页(huge page)优化-和 JVM 的优化策略正好相反。

硬盘

AOF 对硬盘的写压力更大。

AOF 追加阻塞

此外,AOF 体系还存在一个追加阻塞的问题,其流程如下:
AOF 追加阻塞

可以看到,主线程如果在写入 AOF 缓冲区时如果上次 fsync 成功的时间(所谓的 checkpoint)还在两秒内,则会持续写入 AOF 缓冲区。这时候 AOF 缓冲区是不安全的。所以实际上 AOF 机制最多会丢两秒内的信息。而如果主线程阻塞,则 Redis 的性能会急剧下降。

至此为止,我们至少有三种潜在的性能瓶颈:save、fork 和 AOF 的 append 造成的阻塞。