Redis持久化之RDB和AOF

RDB

RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发。

触发机制

手动触发分别对应save 和bgsave

  • save 阻塞redis 服务器,直到RDB过程完成,对于内存比较大的实例会造成长时间阻塞,不建议线上环境使用。
  • bgsave,fork 子进程完成RDB,阻塞只发生在fork 阶段,一般时间很短。

自动触发

  • save m n m表示秒内数据集存在n次修改时,自动触发bgsave。

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

  • 执行 debug reload 命令会重新加载redis,会自动触发save操作。
  • 默认执行shutdown,如果没有开启AOF 会自动执行bgsave。

流程

image-20190425100414425

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

  • 父进程执行fork,fork 过程中会阻塞父进程,通过info stats 查看latest_fork_use 选项,可以获取最近一次fork 的耗时,单位是微秒。

    1
    2
    3
    4
    5
    127.0.0.1:6379> INFO stats
    # Stats
    ……
    latest_fork_usec:612
    ……
  • 父进程fork 完,bgsave 命令会返回 background saveing started 信息,并不在阻塞父进程,可以继续响应其他命令。

    1
    2
    127.0.0.1:6379> BGSAVE
    Background saving started
  • 子进程创建RDB 文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。执行lastsave 命令可以获取最后一次生成RDB 的时间,对应 info 统计中的rdb_last_save_time 选项。

    1
    2
    127.0.0.1:6379> LASTSAVE
    (integer) 1556158402
  • 进程发送信号给父进程表示完成,父进程更新统计信息,具体见 info Persistence下的 rdb_* 相关选项。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
127.0.0.1:6379> INFO stats
# Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1556158402
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:0
rdb_current_bgsave_time_sec:-1
rdb_last_cow_size:24240128
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
aof_last_cow_size:0

配置

RDB 优缺点

RDB的优点:

  • RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份,并把RDB文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复。
  • Redis加载RDB恢复数据远远快于AOF的方式。

RDB的缺点:

  • RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。
  • RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决。

AOF

AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。理解掌握好AOF持久化机制对我们兼顾数据安全性和性能非常有帮助。

使用

配置

1
2
3
4
cat redis.conf | grep ^append
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec

AOF的工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load)

image-20190425103508515

流程

  • 所有写入追加到 aof_buf 中。
  • AOF 缓冲区根据对应的策略向硬盘做同步操作。
  • 随着AOF文件的变大,需要定期对AOF 文件进行重写,达到压缩目的。
  • 当redis 服务器重启时,可以加载AOF文件进行数据恢复。

为什么采用文本方式写入

AOF 写入采用文本方式,理由如下:

  • 兼容性好
  • 开启AOF 后,所有写操作都是追加的,避免了二次处理开销。
  • 文本协议具有可读性,方便直接修改和处理。

为什么要把命令追加到AOF_BUF

redis 使用单线程响应命令,如果每次写AOF 都直接追加到磁盘,性能会很差,先写入缓存区,然后在同步到磁盘性能会有很大提升。

AOF 的文件同步

可配置值 说明
always 命令写人aof_ buf 后调用系统fsync操作同步到AOF文件,fsync 完成后线程返回
everysec 命令写入aof_ buf后调用系统write操作,write完成后线程返回。fsync 同步文件操作由专门线程每秒调用- -次
no 命令写人aof_ buf 后调用系统write操作,不对AOF文件做fsync同步,同步硬盘操作由操作系统负责,通常同步周期最长30秒

系统调用write和fsync说明:

  • write操作会触发延迟写(delayed write)机制。Linux在内核提供页缓冲区用来提高硬盘IO性能。write操作在写入系统缓冲区后直接返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区页空间写满或达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。
  • fsync针对单个文件操作(比如AOF文件),做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回,保证了数据持久化。

AOF 重写

随着不断写入AOF 文件不断变大,redis 通过AOF 重写机制压缩文件体积。把进程内的数据转化为写命令同步到新的AOF文件。

重写AOF的优势

  • 降低文件占用,提升redis 加载速度。

AOF 分手动和自动触发

  • 手动: bgrewriteaof
  • 自动,根据配置文件中的下面2个参数确定自动触发时间
1
2
3
# cat redis.conf |grep ^auto-aof
auto-aof-rewrite-percentage 100 # 表示当前AOF 文件空间(aof_current_size)和上一次重写后AOF文件空间(aof_base_size)的比
auto-aof-rewrite-min-size 64mb # 表示运行AOF重写时的文件最小体积

自动触发时机=aof_current_size>auto-aof-rewrite-minsize&&(aof_current_size-aof_base_size)/aof_base_size>=auto-aof-rewritepercentage其中aof_current_size和aof_base_size可以在info Persistence统计信息中查。

流程说明

  • 执行AOF 重写请求,如果当前有进程正在执行AOF重写,请求不执行并返回

    1
    ERR Background append only file rewriting already in progre

    如果当前正在执行bgsave,重写命令延迟到bgsave 完成后执行,返回

    1
    Background append only file rewriting schedul
  • 父进程fork 子进程,开销等同于bgsave。

    • 主进程 fork 后,继续执行其他命令,所有修改依然写入AOF 缓冲区,并根据appendfsync 策略同步到磁盘。
    • 由于fork 操作运用写时复制技术,子进程只贡献fork 操作时的内存数据,由于父进程依然响应命令,redis 使用AOF重写缓冲区保存这部分新数据,防止新AOF文件生成时丢失这部分数据。
  • 子进程根据内存快照,按命令合并规则写入到新的AOF 文件。每次批量写入磁盘数据由配置置aof-rewrite-incremental-fsync控制,默认为32MB,防止单次刷盘数据过多造成硬盘阻塞。

  • 新AOF 文件写入后,子进程发送信号给父进程,更新统计信息。
  • 父进程把AOF 重写缓存区的数据写入到新AOF文件。
  • 使用新AOF文件替换老文件,完成AOF重写。

问题定位与优化

fork 操作耗时问题排查

redis 做 RDB 或 AOF 重写时,会进行fork,虽然fork创建的子进程不需要拷贝父进程的物理内存空间,但是会复制父进程的空间内存页表。例如对于10GB的Redis进程,需要复制大约20MB的内存页表,因此fork操作耗时跟进程总内存量息息相关,如果使用虚拟化技术,特别是Xen虚拟机,fork操作会更耗时。

如何改善fork操作的耗时:
1)优先使用物理机或者高效支持fork操作的虚拟化技术,避免使用Xen。
2)控制Redis实例最大可用内存,fork耗时跟内存量成正比,线上建议每个Redis实例内存控制在10GB以内。
3)合理配置Linux内存分配策略,避免物理内存不足导致fork失败。
4)降低fork操作的频率,如适度放宽AOF自动触发时机,避免不必要的全量复制等。

子进程开销监控和优化

CPU

  • CPU开销分析。子进程负责把进程内的数据分批写入文件,这个过程属于CPU密集操作,通常子进程对单核CPU利用率接近90%.

  • CPU消耗优化。Redis是CPU密集型服务,不要做绑定单核CPU操作。由于子进程非常消耗CPU,会和父进程产生单核资源竞争。

  • 不要和其他CPU密集型服务部署在一起,造成CPU过度竞争。
  • 如果部署多个Redis实例,尽量保证同一时刻只有一个子进程执行重写工作。

内存

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

  • 内存消耗监控。

磁盘

通过iostat 等监控磁盘负载。