# 三、Redis 持久化

通常数据库存在三种用于持久操作以防止数据损坏的常见策略:

  1. 数据库不关心故障,而是在数据文件损坏后从数据备份或快照中恢复。RDB 就是这种情况。
  2. 使用日志记录每个操作的操作行为,以在失败后通过日志恢复一致性。由于操作日志是按顺序追加写入的,因此不会出现无法恢复操作日志的情况。类似于 MySQL 的 redo log 和 undo log。
  3. 数据库不修改旧数据,而仅通过追加进行写入,因为数据本身就是日志,因此永远不会出现数据无法恢复的情况,CouchDB 是一个很好的例子。AOF 类似这种情况。

严格上讲 Redis 为持久化提供了三种方式:

  • RDB:在指定时间间隔能对数据进行快照存储,类似 MySQL 的 dump 备份文件。
  • AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据(MySQL 的 binlog)。
  • RDB 与 AOF 混合使用:这是 Redis4.0 开始的新特性。在混合使用中 AOF 读取 RDB 数据重建原始数据集,集二者优势为一体。

# 1. RDB 持久化

# 1.1 初始化环境

# 1.1.1 创建配置/数据/日志目录
# 创建配置目录
mkdir -p /usr/local/redis/conf
# 创建数据目录
mkdir -p /usr/local/redis/data
# 创建日志目录
mkdir -p /usr/local/redis/log
# 1.1.2 配置文件

创建一份配置文件至 conf 目录。

vim /usr/local/redis/conf/redis.conf

内容如下:

# 放行 IP 访问限制
bind 0.0.0.0
# 后台启动
daemonize yes
# 日志存储目录及日志文件名
logfile "/usr/local/redis/log/redis.log"
# RDB 数据文件名
dbfilename dump.rdb
# RDB 数据文件和 AOF 数据文件的存储目录
dir /usr/local/redis/data
# 设置密码
requirepass 123456

# 1.2 开启 RDB

redis.conf 文件末尾加上:

# 900 秒内如果超过 1 个key改动,则发起快照保存
save 900 1
# 300 秒内如果超过 10 个 key 改动,则发起快照保存
save 300 10
# 60 秒内如果超过 1W 个 key 改动,则发起快照保存
save 60 10000

# 1.3 Redis 快照

快照,顾名思义可以理解为拍照一样,把整个内存数据映射到硬盘中,保存一份到磁盘,因此恢复数据起来比较快,把数据映射回去即可,不像 AOF 一样,一条条的执行操作指令

快照是默认的持久化方式。这种方式就是将内存中数据以快照的放入写入二进制文件中,默认的文件名为 dump.rdb,可以通过配置设置自动做快照持久化的方式。

产生快照的情况有以下几种:

  • 手动 bgsave 执行(不会阻塞,后台一点点备份)。
  • 手动 save 执行(会阻塞,不接受客户端命令,备份完了才放开)。
  • 根据配置文件自动执行。
  • 客户端发送 shutdown,系统会先执行 save 命令阻塞客户端,然后关闭服务器。
  • 当有主从架构时,从服务器向主服务器发送 sync 命令来执行复制时,主服务器会执行 bgsave 操作。

# 1.4 RDB 工作原理

Redis 默认会将快照文件存储在 Redis 当前进程的工作目录中的 dump.rdb 文件中,可以通过配置 dirdbfilename 两个参数分别制定快照文件的存储路径和文件名。

RDB 流程如下(rdb.c 文件中):

image-20210413103244049

# 1.5 RDB 优点

  1. 紧凑压缩的二进制文件。
  2. fork 子进程性能最大化(非阻塞)。
  3. 启动效率高(直接进行数据映射,而非一行行执行操作)。

# 1.6 RDB 缺点

  1. 生成快照的时机问题(时间还没到就宕机了,那会丢失这部分时间的数据)。
  2. fork 子进程的开销问题。

# 2. AOF 持久化

AOF 也是 Redis 持久化的重要手段之一,AOF(Append Only File)只追加文件,也就是每次处理完请求命令后都会将此命令追加到 .aof 文件的末尾。而 RDB 是压缩成二进制等时机开子进程去干这件事。

# 2.1 开启 AOF

通过配置 redis.conf 进行启动,默认是关闭的

# 默认 appendonly 为 no
appendonly yes
appendfilename "appendonly.aof"
# RDB 文件和 AOF 文件所在目录
dir /usr/local/redis/data

# 2.2 同步策略

Redis 中提供了 3 种 AOF 同步策略:

  • 每秒同步(默认,每秒调用一次 fsync,这种模式性能并不是很糟糕)。
  • 每修改同步(会极大削弱 Redis 的性能,因为这种模式下每次 write 后都会调用 fsync)。
  • 不主动同步(由操作系统自动调度刷盘,Linux 是 30s 一次,性能是最好的)。
# 每秒钟同步一次,默认
appendfsync everysec
# 每次有数据修改发生时都会写入 AOF 文件
appendfsync always
# 从不同步,由操作系统自动调度刷盘,高调但是数据不会主动被持久化
appendfsync no

# 2.3 AOF 工作原理

image-20210803112028852

AOF 的频率高的话肯定会对 Redis 带来性能影响,因为每次都是刷盘操作,跟 MySQL 一样了。

Redis 每次都是先将命令放到缓存区,然后根据具体策略(每秒/每条指令/缓冲区满)进行刷盘操作。

如果配置 always,那么就是典型阻塞,如果是 everysec 每秒的话,那么会开一个同步线程去每秒进行刷盘操作,对主线程影响稍小。

# 2.4 写入文件与恢复

AOF 文件是一个只进行 append 操作的日志文件,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。假如一次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在 Redis 下一次启动之前,我们可以通过 redis-check-aof 工具来帮助我们解决问题。

AOF 文件有序地保存了对数据库执行的所有写人操作,这些写入操作以 Redis 协议的格式保存,因此 AOF 文件的内存非常容易被人读懂,对文件进行分析(parse)也很轻松。

导出(export)AOF 文件也非常简单。举个例子,如果你不小心执行了 FLUSHALL 命令,但只要 AOF 文件没有被重写,那么只要停止服务器,移除 AOF 文件末尾的 FLUSHALL 命令,并重启 Redis,就可以将数据集恢复到 FLUSHALL 执行之前的状态。

# 2.5 重写

随着运行时间的增长,执行的命令越来越多,会导致 AOF 文件越来越大,当 AOF 文件过大时,Redis 会执行重写机制来压缩 AOF 文件。这个压缩和上面提到的 RDB 文件的算法压缩不同,重写机制主要是将文件中无效的命令去除。比如:

  • 同一个 key 的值,只保留最后一次写入。
  • 已删除或者已过期数据相关命令会被去除。这样就避免了 aof 文件过大而实际内存数据小的问题(如频繁修改数据时,命令很多,实际数据很少)。

# 2.5.1 重写时机

  • 手动执行 bgrewriteaof 触发AOF重写。

  • redis.conf文件中配置重写的条件,如:

    # 当文件小于64M时不进行重写
    auto-aof-rewrite-min-size 64MB  
    # 当文件比上次重写后的文件大 100% 时进行重写
    auto-aof-rewrite-min-percenrage 100
    

# 2.5.2 重写过程

  1. 从主进程中 fork 出子进程,并拿到 fork 时的 AOF 文件数据写到一个临时 AOF 文件中。
  2. 在重写过程中,Redis 收到的命令会继续追加到现有的 AOF 文件里面。
  3. 重写完成后通知主进程,主进程会将 AOF 缓冲区中的数据追加到子进程生成的文件中,这样保证重写不丢失重写过程中的命令。
  4. Redis 会原子的将旧文件替换为新文件,并开始将数据写入到新的 AOF 文件上。

# 2.5.3 重写注意点

  1. 执行重写时如果发现有进程正在执行重写,那么直接返回。
  2. 如果有进程正在执行 bgsave,那么会等 bgsave 执行完成后再执行重写。
  3. Redis 执行重写时会 fork 一个进程进行,其开销和 RDB 一样。
  4. 重写过程不影响原有的 AOF 过程,write,save 操作都不影响。
  5. 重写过程中有几种时刻会阻塞主进程: 在 fork 子进程时,将重写缓冲区的数据写到磁盘上时,使用新 AOF 文件替换旧文件时。

# 2.6 常用配置

# fysnc 持久化策略
appendfsync everysec
# AOF 重写期间是否禁止 fsync。如果开启该选项,可以减轻文件重写时 CPU 和影片的负载(尤其是硬盘),但是会丢失 AOF 重写期间的数据,因此我们需要在负载和安全性之间进行平衡。
no-appendfsync-on-rewrite no
# 当前 AOF 文件大于多少字节后才触发重写
auto-aof-rewrite-min-size 64mb
# 当文件比上次重写后的文件大 100% 时进行重写,也就是2倍时触发 rewrite
auto-aof-rewrite-percentage 100
# 如果 AOF 文件结尾损耗,Redis 启动时是否仍加载 AOF 文件
aof-load-truncated yes

# 2.7 AOF 优点

  1. 数据不易丢失(顶多就丢失一两秒的数据)。
  2. 自动重写机制。
  3. 易懂易恢复。

# 2.8 AOF 缺点

  1. AOF 文件恢复数据慢。
  2. AOF 持久化效率低。
  3. 可能会存储下错误操作,影响后面数据恢复。

# 3. 如何选择 RDB 与 AOF

# 3.1 同时开启

Redis 先加载 AOF 文件来恢复原始数据,因为 AOF 数据比 RDB 更完整,但是 AOF 存在潜在的 Bug,如把错误的操作记录写入了 AOF,会导致数据恢复失败,所以可以把 RDB 作为后备数据。

为了考虑性能,可以只在 Slave 上开启 RDB,并且 15min 备份一次,如果为了避免 AOF rewrite 的 IO 以及阻塞,可以在 Redis 集群中不开启 AOF,靠集群的备份机制来保证可用性,在启动时选取较新的 RDB 文件,如果集群全部崩溃,会丢失 15min 前的数据。

# 3.2 混合模式

Redis4.0 开始支持该模式。

解决的问题:Redis 在重启时通常是加载 AOF 文件,但加载速度慢。因为 RDB 数据不完整,所以加载 AOF。

开始方式:

aof-user-rdb-preamble true

开启后,AOF 在重写时会直接读取 RDB 中的内容

运行过程:通过 bgrewriteaof 完成,不同的是当开启混合持久化后:

  1. 子进程会把内存中的数据以 RDB 的方式写入 AOF 文件中。
  2. 把重写缓冲区中的增量命令以 AOF 方式写入到文件。
  3. 将含有 RDB 格式和 AOF 格式的 AOF 数据覆盖旧的 AOF 文件。

新的 AOF 文件中,一部分数据来自 RDB 文件,一部分来自 Redis 运行过程中的增量数据。

# 3.3 数据恢复

当我们开启了混合持久时,启动 Redis 依然先加载 AOF 文件,AOF 文件加载可能有以下两种情况:

  1. AOF 文件开头是 RDB 的格式,先加载 RDB 内容再加载剩余的 AOF。
  2. AOF 文件开始不是 RDB 的格式,直接以 AOF 格式加载整个文件。

# 3.4 优点

  1. 既能快速备份和恢复数据又能避免大量数据丢失。

# 3.5 缺点

  1. RDB 是二进制压缩格式,AOF 在读取它时可读性较差。

# 4. 二者动态切换

在 Redis2.2 或以上版本,可以在不重启的情况下,从 RDB 切换到 AOF:

  • 为最近的 dump.rdb 文件创建一个备份。

  • 将备份放到一个安全的地方。

    cp dump.rdb dump.rdb.bak
    
  • 执行以下两条命令

    # 开启 AOF
    redis-cli config set appendonly yes
    # 关闭 RDB
    redis-cli config set save ""
    
    • 确保写命令会被正确地追加到 AOF 文件的末尾。
    • 执行的第一条命令开启了 AOF 功能:Redis 会阻塞直到初始 AOF 文件创建完成为止,之后 Redis 会继续处理命令请求,并开始将写入命令追加到 AOF 文件末尾。

# 5. 容灾备份

1. 开启 RDB 持久化

save 900 1

2. 开启 AOF 配置

# 开启
appendonly yes
appendfilename "appendonly.aof"

# 重写
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage 100

# 追加
appendfsync everysec

3. RDB 日志备份,编写脚本定时备份

vim 一个 redis-rdb-copy-per-hour.sh

#!bin/bash
cur_date=$(date "+%Y%m%d%H%M%S")
rm -rf /usr/local/redis/snapshotting/$cur_date
mkdir -p /usr/local/redis/snapshotting/$cur_date
cp /usr/local/redis/data/dump.rdb /usr/local/redis/snapshotting/$cur_date

del_date=$(date -d -48hour "+Y%%m%d%H%M")
rm -rf /usr/local/redis/snapshotting/$del_date

使用 crontab 定时器执行备份脚本

# 打开 crontab
crontab -e

然后写入(以下为每 10 秒执行一次,生产环境考验调整为每小时执行一次):

*/1 * * * * sh /usr/local/redis/bin/redis-rdb-copy-per-hour.sh
*/1 * * * * sleep 10; /usr/local/redis/bin/redis-rdb-copy-per-hour.sh
*/1 * * * * sleep 20; /usr/local/redis/bin/redis-rdb-copy-per-hour.sh
*/1 * * * * sleep 30; /usr/local/redis/bin/redis-rdb-copy-per-hour.sh
*/1 * * * * sleep 40; /usr/local/redis/bin/redis-rdb-copy-per-hour.sh
*/1 * * * * sleep 50; /usr/local/redis/bin/redis-rdb-copy-per-hour.sh

保存即可。

# 6. 优化方案

  1. 独立部署,硬盘优化
    • 持久化过程是密集计算的过程,所以最好把 Redis 单独部署到一台服务器上。
    • 写入请求多的话,可以考虑性能更高的 SSD 磁盘。
  2. 缓存禁用持久化
    • 缓存丢失了也不要紧。
  3. 主从模式,从持久化
    • 从节点一般只提供读功能,把持久化的工作交给从节点,减轻主节点的压力。
    • AOF 可以考虑禁用,因为多个从节点我们只需要选取追新的那份备份就可以了。
  4. 优化 fork 处理
    • 降低 AOF 的重写频率。
    • 重写期间如果已经阻塞的时候,那就不接收重写期间的数据了。
上次更新: 8/4/2021, 12:47:55 PM