Redis 持久化

Redis 有两种持久化的方式: 快照 (RDB文件) 和追加式文件 (AOF文件):

  • RDB 持久化方式会在一个特定的间隔保存那个时间点的一个数据快照.
  • AOF 持久化方式则会记录每一个服务器收到的写操作. 在服务启动时, 这些记录的操作会逐条执行从而重建出原来的数据. 写操作命令记录的格式跟 Redis 协议一致, 以追加的方式进行保存.
  • Redis 的持久化是可以禁用的, 就是说你可以让数据的生命周期只存在于服务器的运行时间里.
  • 两种方式的持久化是可以同时存在的, 但是当 Redis 重启时, AOF文件会被优先用于重建数据.

RDB

工作原理

  • Redis 调用 fork(), 产生一个子进程.
  • 子进程把数据写到一个临时的 RDB 文件.
  • 当子进程写完新的 RDB 文件后, 把旧的 RDB 文件替换掉.

文件路径和名称

默认 Redis 会把快照文件存储为当前目录下一个名为 dump.rdb 的文件. 要修改文件的存储路径和名称, 可以通过修改配置文件 redis.conf 实现:

# RDB文件名,默认为dump.rdb。
dbfilename dump.rdb

# 文件存放的目录,AOF文件同样存放在此目录下。默认为当前工作目录。
dir ./

保存点 (RDB的启用和禁用)

你可以配置保存点, 使 Redis 如果在每 N 秒后数据发生了 M 次改变就保存快照文件. 例如下面这个保存点配置表示每 60 秒, 如果数据发生了 1000 次以上的变动, Redis就会自动保存快照文件:

save 60 1000

保存点可以设置多个, Redis 的配置文件就默认设置了 3 个保存点:

# 格式为:save <seconds> <changes>
# 可以设置多个。
save 900 1 #900秒后至少1个key有变动
save 300 10 #300秒后至少10个key有变动
save 60 10000 #60秒后至少10000个key有变动

如果想禁用快照保存的功能, 可以通过注释掉所有 "save" 配置达到,或者在最后一条 "save" 配置后添加如下的配置:

save ""

错误处理

默认情况下, 如果 Redis 在后台生成快照的时候失败, 那么就会停止接收数据, 目的是让用户能知道数据没有持久化成功.

但是如果你有其他的方式可以监控到 Redis 及其持久化的状态, 那么可以把这个功能禁止掉.

stop-writes-on-bgsave-error yes

数据压缩

默认 Redis 会采用 LZF 对数据进行压缩.

如果你想节省点 CPU 的性能, 你可以把压缩功能禁用掉, 但是数据集就会比没压缩的时候要大.

rdbcompression yes

数据校验

从版本 5 的 RDB 的开始, 一个 CRC64 的校验码会放在文件的末尾. 这样更能保证文件的完整性, 但是在保存或者加载文件时会损失一定的性能 (大概10%).

如果想追求更高的性能, 可以把它禁用掉, 这样文件在写入校验码时会用 0 替代, 加载的时候看到 0 就会直接跳过校验.

rdbchecksum yes

手动生成快照

Redis 提供了两个命令用于手动生成快照.

BGSAVE

BGSAVE 命令使用后台的方式保存 RDB 文件, 调用此命令后, 会立刻返回OK.

Redis 会产生一个子进程进行快照写入硬盘. 父进程继续处理命令请求.

SAVE

SAVE 命令会使用同步的方式生成 RDB 快照文件, 这意味着在这个过程中会阻塞所有其他客户端的请求.

因此不建议在生产环境使用这个命令.

重点

  1. 如果用户设置了 save 60 1000, 那么从 Redis 最近一次创建快照之后开始算起. 当条件被满足时, 就会自动出发 BGSAVE 命令. 如果有多个 SAVE 配置, 那么当任意一个被满足时, 都会出发一次 BGSAVE 命令.
  2. 当 Redis 通过 SHUTDOWN 命令, 来关闭服务时, 或者接收到标准 TERM 信号时, 会执行一次 SAVE 命令, 阻塞所有客户端, 并且执行完毕后会关闭 Redis 服务.

Redis 持久化

这里注意的是 fork 操作会阻塞, 导致 Redis 读写性能下降. 我们可以控制单个 Redis 实例的最大内存, 来尽可能降低 Redis 在 fork 时的事件消耗. 以及上面提到的自动触发的频率减少 fork 次数, 或者使用手动触发, 根据自己的机制来完成持久化.

AOF

快照并不是很可靠. 如果你的电脑突然宕机了, 或者电源断了, 又或者不小心杀掉了进程, 那么最新的数据就会丢失.

而 AOF 文件则提供了一种更为可靠的持久化方式. 每当 Redis 接受到会修改数据集的命令时, 就会把命令追加到 AOF 文件里, 当你重启 Redis 时, AOF 里的命令会被重新执行一次, 重建数据.

启用 AOF

把配置项 appendonly 设为 yes:

appendonly yes

文件路径和名称

# 文件存放目录,与RDB共用。默认为当前工作目录。
dir ./

# 默认文件名为appendonly.aof
appendfilename "appendonly.aof"

可靠性

你可以配置 Redis 调用 fsync 的频率, 有三个选项:

  • 每当有新命令追加到 AOF 的时候调用 fsync. 速度最慢, 最安全.
  • 每秒 fsync 一次. 速度快 (2.4版本跟快照方式速度差不多), 安全性不错 (最多丢失 1 秒的数据).
  • 从不 fsync, 交由系统去处理. 这个方式速度最快, 但是安全性一般.

推荐使用每秒 fsync 一次的方式 (默认的方式), 因为它速度快, 安全性也不错. 相关配置如下:

# appendfsync always
appendfsync everysec
# appendfsync no

对于增量追加到文件这一步主要的流程是: 命令写入=》追加到 aof_buf =》同步到aof磁盘. 那么这里为什么要先写入 buf 在同步到磁盘呢? 如果实时写入磁盘会带来非常高的磁盘IO, 影响整体性能.

AOF 重写是为了减少 AOF 文件的大小, 随着写操作的不断增加, AOF 文件会越来越大. 例如你递增一个计数器 100 次, 那么最终结果就是数据集里的计数器的值为最终的递增结果, 但是 AOF 文件里却会把这 100 次操作完整的记录下来.

而事实上要恢复这个记录, 只需要 1 个命令就行了, 也就是说 AOF 文件里那 100 条命令其实可以精简为 1 条. 所以 Redis 支持这样一个功能: 在不中断服务的情况下在后台重建 AOF 文件.

Redis 持久化

对于上图有四个关键点补充一下:

  • 在重写期间, 由于主进程依然在响应命令, 为了保证最终备份的完整性; 因此它依然会写入旧的 AOF file 中, 如果重写失败, 能够保证数据不丢失.
  • 为了把重写期间响应的写入信息也写入到新的文件中, 因此也会为子进程保留一个 buf, 防止新写的 file 丢失数据.
  • 重写是直接把当前内存的数据生成对应命令, 并不需要读取老的 AOF 文件进行分析、命令合并.
  • AOF 文件直接采用的文本协议, 主要是兼容性好、追加方便、可读性高可认为修改修复.

性能与实践

通过上面的分析, 我们都知道 RDB 的快照、AOF的重写都需要 fork, 这是一个重量级操作, 会对 Redis 造成阻塞. 因此为了不影响 Redis 主进程响应, 我们需要尽可能降低阻塞.

  • 降低 fork 的频率, 比如可以手动来触发 RDB 生成快照、与AOF重写;
  • 控制 Redis 最大使用内存, 防止 fork 耗时过长;
  • 使用更牛逼的硬件;
  • 合理配置 Linux 的内存分配策略, 避免因为物理内存不足导致 fork 失败.

在线上我们到底该怎么做? 我提供一些自己的实践经验.

  • 如果 Redis 中的数据并不是特别敏感或者可以通过其它方式重写生成数据, 可以关闭持久化;
  • 自己制定策略定期检查 Redis 的情况, 然后可以手动触发备份、重写数据;
  • 单机如果部署多个实例, 要防止多个机器同时运行持久化、重写操作, 防止出现内存、CPU、IO资源竞争, 让持久化变为串行;
  • 可以加入主从机器, 利用一台从机器进行备份处理, 其它机器正常响应客户端的命令;
  • RDB 持久化与 AOF 持久化可以同时存在, 配合使用.