JohnShen's Blog.

Redis-持久化

字数统计: 2.7k阅读时长: 10 min
2020/08/29 Share

RDB

1. 触发

可以分为手动触发和自动触发:

手动触发

  • bgsave 命令,Redis 进程执行 fork 操作创建子进程,RDB过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。
  • save 命令,已舍弃,会阻塞 Redis 直至 RDB 完成。

自动触发

内部自动触发使用的都是 bgsave 的形式

  • 配置文件中的 save 配置:save m n 表示m秒内数据集存在 n 次修改时,自动触发 bgsave。

  • 从节点执行全量复制操作,主节点自动执行 bgsave 生成 RDB 文件并发送给从节点。

  • 执行 shutdown 时,如果没有开启 aof,则会自动执行 bgsave。

  • 执行 debug reload 时。

    debug reload 用于更改配置后重新加载 Redis,RunID不变(RunID 如若改变,会使从节点重新做全量复制),从而避免不必要的全量复制。但 debug reload 会阻塞当前Redis节点主线程,阻塞期间会生成本地 RDB 快照并清空数据之后再加载 RDB 文件。

2. 执行流程

1
2
3
4
15718:M 29 Aug 12:24:08.523 * 1 changes in 900 seconds. Saving...
15718:M 29 Aug 12:24:08.529 * Background saving started by pid 88458
88458:C 29 Aug 12:24:08.535 * DB saved on disk
15718:M 29 Aug 12:24:08.631 * Background saving terminated with success

当执行 bgsave 命令后:

  1. 父进程判断当前是否存在正在执行的子进程,如 RDB/AOF 子进程,如果存在 bgsave 命令直接返回;

  2. 父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞;

  3. fork 完成后便返回 Background saving started信息,不再阻塞父进程;

  4. 子进程创建 RDB 文件(紧凑的二进制文件),根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换;

  5. 子进程发送信号给父进程表示已完成,父进程更新统计信息。

文件位置和命名由配置文件中的dirdbfilename所决定。手动 config set dir {newDir}config set dbfilename {newFileName}运行期动态执行。

RDB 文件可以使用 redis-check-dump 工具进行校验,加载损坏的 RDB 文件时 Redis 会拒绝启动。

3. 优缺点

优点

非常适合于备份、全量复制等场景,也常用于灾难恢复。且其加载速度会远远快于 AOF。

缺点

实时持久化/秒级持久化无法支持(bgsave的 fork 成本较高,属于重量级操作)。

老版本 Redis 可能无法支持新版本的 RDB 文件(RDB格式有多版)。

AOF

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

AOF 默认不开启, 配置文件中需要设置 appendonly yes,AOF 文件名通过appendfilename配置,由dir指定路径。

0. 处理流程

  1. 所有的写入命令追加到 aof_buf(缓冲区);
  2. AOF 缓冲区根据对应的策略(always、everysec、no)向硬盘做同步操作;
  3. 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的;
  4. 当 Redis 服务器重启时,可以加载 AOF 文件进行数据恢复。

1. 命令写入

写入的内容直接是文本协议格式(兼容性好、有可读性,便于直接修改处理)。

引入aof_buf:Redis 使用单线程响应命令,直接落盘性能完全由磁盘左右。同时,缓冲区同步磁盘的策略也可由用户作出平衡。

2. 文件同步

即 AOF 缓冲区同步到磁盘。appendfsync 配置的可选参数如下:

策略 说明 建议
always 命令写入buf后调用fsync同步到AOF文件,fsync完成后线程返回 性能较差,不建议配置
everysec 命令写入buf后调用write,write完成后返回。fsync同步文件操作由专门线程每秒执行1次 建议,且为默认,兼顾性能和数据安全性。系统突然宕机只丢失少量数据
no 命令写入buf后调用write,不做fsync同步。同步磁盘操作由操作系统完成,一般周期最长30s 操作系统每次同步AOF文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但数据安全性无法保证
  • write操作会触发延迟写(delayed write)机制。Linux在内核提供页缓冲区用来提高硬盘IO性能。write在写入系统缓冲区后直接返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区页空间写满或达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。
  • fsync针对单个文件操作(比如AOF文件),做强制硬盘同步,fsync 将阻塞直到写入硬盘完成后返回,保证了数据持久化。

3. 重写机制

Redis 使用 AOF 重写机制压缩文件体积,重写后的 AOF 文件不会包含超时的数据,重写直接使用进程内数据直接生成,不会包含无效的命令,多条写命令也可合并为一个。最终更小的 AOF 文件能被 Redis 更快的加载。

AOF 重写的触发:

  • 手动 bgrewriteaof

  • 自动:auto-aof-rewrite-min-size(AOF重写时文件最小体积,默认为64MB)和auto-aof-rewrite-percentage(当前AOF文件空间aof_current_size和上一次重写后AOF文件空间aof_base_size的比值)参数确定自动触发时机。

    1
    aof_current_size > auto-aof-rewrite-min-size &&(aof_current_size - aof_base_size)/ aof_base_size >= auto-aof-rewrite-percentage
流程
  1. 如果当前进程正在执行AOF重写,请求不执行,如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成之后再执行;

  2. 父进程执行fork创建子进程;

  3. 主进程 fork 操作完成后响应其他命令。所有修改命令依然写入AOF 缓冲区并根据 appendfsync 策略同步到硬盘;

    由于fork操作运用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然响应命令,Redis使用AOF重写缓冲区保存这部分新数据,防止新AOF文件生成期间丢失这部分数据;

  4. 子进程根据内存快照,按照命令合并规则写入到新的AOF文件。每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制,默认为32MB,防止单次刷盘数据过多造成硬盘阻塞;

  5. 新AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息;

  6. 父进程把AOF重写缓冲区的数据写入到新的AOF文件;

  7. 使用新AOF文件替换老文件,完成AOF重写。

4. 重启加载

AOF持久化开启且存在AOF文件时,优先加载AOF文件

AOF关闭或者AOF文件不存在时,加载RDB文件

加载AOF/RDB文件成功后,Redis启动成功

AOF/RDB文件存在错误时,Redis启动失败并打印错误信息。

对于错误格式的AOF文件,先进行备份,然后采用redis-check-aof --fix命令进行修复,修复后使用diff -u对比数据的差异,找出丢失的数据,有些可以人工修改补全。

AOF文件可能存在结尾不完整的情况(如机器突然掉电),Redis 为我们提供aof-load-truncated配置来兼容这种情况,默认开启。加载 AOF 时,当遇到此问题时会忽略并继续启动。

补充

Fork

  • fork 是个重量级操作,虽然 fork 创建的子进程不需要拷贝父进程的物理内存空间,但是会复制父进程的空间内存页表。例如对于 10GB 的 Redis 进程,需要复制大约 20MB 的内存页表,因此 fork 操作耗时跟进程总内存量息息相关。

  • 正常情况下fork耗时应该是每GB消耗20毫秒左右。

  • 改善 fork 操作的耗时:控制Redis实例最大可用内存,fork耗时跟内存量成正比,线上建议每个Redis实例内存控制在10GB以内。

  • 子进程通过fork操作产生后,占用内存大小等同于父进程,理论上需要两倍的内存来完成持久化操作,但 Linux 有写时复制机制(copy-on-write)。父子进程会共享相同的物理内存页,当父进程处理写请求时会把要修改的页创建副本,而子进程在 fork 操作过程中共享整个父进程内存快照。

  • fork 出来的子进程把数据写到文件也属于CPU密集操作。

  • 避免在大量写入时,做子进程重写操作,这样将导致父进程维护大量页副本,造成内存消耗。

  • 如果重写过程中存在内存修改操作,父进程负责创建所修改内存页的副本,日志中看出这部分内存消耗了5MB,可以等价认为RDB重写消耗了5MB的内存。

    1
    2
    3
    4
    * Background saving started by pid 7692
    * DB saved on disk
    * RDB: 5 MB of memory used by copy-on-write
    * Background saving terminated with success

    AOF 重写时需要重写缓冲区,因此根据日志可以预估内存消耗为:53MB + 1.49MB,就是AOF重写时子进程消耗的内存量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    * Background append only file rewriting started by pid 8937
    * AOF rewrite child asks to stop sending diffs.
    * Parent agreed to stop sending diffs. Finalizing AOF...
    * Concatenating 0.00 MB of AOF diff received from parent.
    * SYNC append only file rewrite performed
    * AOF rewrite: 53 MB of memory used by copy-on-write
    * Background AOF rewrite terminated with success
    * Residual parent diff successfully flushed to the rewritten AOF (1.49 MB)
    * Background AOF rewrite finished successfully
  • AOF重写时会消耗大量硬盘IO。

AOF追加阻塞

同步硬盘的策略是everysec时,使用另一条线程每秒执行fsync同步硬盘。当系统硬盘资源繁忙时,会造成Redis主线程阻塞。

主线程负责写入AOF缓冲区。AOF线程负责每秒执行一次同步磁盘操作,并记录最近一次同步时间。主线程负责对比上次AOF同步时间:

  • 如果距上次同步成功时间在2秒内,主线程直接返回。
  • 如果距上次同步成功时间超过2秒,主线程将会阻塞,直到同步操作完成。

所以:配置 everysec 时,最多可能丢失 2 秒数据,不是 1 秒;如果系统 fsync 缓慢,将会导致 Redis 主线程阻塞影响效率。

CATALOG
  1. 1. RDB
    1. 1.1. 1. 触发
      1. 1.1.1. 手动触发
      2. 1.1.2. 自动触发
    2. 1.2. 2. 执行流程
    3. 1.3. 3. 优缺点
      1. 1.3.1. 优点
      2. 1.3.2. 缺点
  2. 2. AOF
    1. 2.0.1. 0. 处理流程
    2. 2.0.2. 1. 命令写入
    3. 2.0.3. 2. 文件同步
    4. 2.0.4. 3. 重写机制
      1. 2.0.4.1. 流程
    5. 2.0.5. 4. 重启加载
  • 3. 补充
    1. 3.1. Fork
    2. 3.2. AOF追加阻塞