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 | 15718:M 29 Aug 12:24:08.523 * 1 changes in 900 seconds. Saving... |
当执行 bgsave 命令后:
父进程判断当前是否存在正在执行的子进程,如 RDB/AOF 子进程,如果存在 bgsave 命令直接返回;
父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞;
fork 完成后便返回
Background saving started
信息,不再阻塞父进程;子进程创建 RDB 文件(紧凑的二进制文件),根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换;
子进程发送信号给父进程表示已完成,父进程更新统计信息。
文件位置和命名由配置文件中的
dir
和dbfilename
所决定。手动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. 处理流程
- 所有的写入命令追加到 aof_buf(缓冲区);
- AOF 缓冲区根据对应的策略(always、everysec、no)向硬盘做同步操作;
- 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的;
- 当 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
流程
如果当前进程正在执行AOF重写,请求不执行,如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成之后再执行;
父进程执行fork创建子进程;
主进程 fork 操作完成后响应其他命令。所有修改命令依然写入AOF 缓冲区并根据 appendfsync 策略同步到硬盘;
由于fork操作运用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然响应命令,Redis使用AOF重写缓冲区保存这部分新数据,防止新AOF文件生成期间丢失这部分数据;
子进程根据内存快照,按照命令合并规则写入到新的AOF文件。每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制,默认为32MB,防止单次刷盘数据过多造成硬盘阻塞;
新AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息;
父进程把AOF重写缓冲区的数据写入到新的AOF文件;
使用新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 successAOF 重写时需要重写缓冲区,因此根据日志可以预估内存消耗为: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 successfullyAOF重写时会消耗大量硬盘IO。
AOF追加阻塞
同步硬盘的策略是everysec时,使用另一条线程每秒执行fsync同步硬盘。当系统硬盘资源繁忙时,会造成Redis主线程阻塞。
主线程负责写入AOF缓冲区。AOF线程负责每秒执行一次同步磁盘操作,并记录最近一次同步时间。主线程负责对比上次AOF同步时间:
- 如果距上次同步成功时间在2秒内,主线程直接返回。
- 如果距上次同步成功时间超过2秒,主线程将会阻塞,直到同步操作完成。
所以:配置 everysec 时,最多可能丢失 2 秒数据,不是 1 秒;如果系统 fsync 缓慢,将会导致 Redis 主线程阻塞影响效率。