Redis持久化

  • 首先是一张整理好的思维导图

Redis持久化有两种方式,rdb和aof:

  • rdb会保存一个时间点的数据快照
  • aof会记录每一个服务器收到的写操作。在服务器启动时,这些记录的操作会逐行从aof文件中执行,从而重建原先的数据集。写操作命令记录的格式与Redis协议一致,以追加的方式进行保存
  • Redis持久化可以禁用
  • Redis两种持久化方式可以同时存在,重启redis时,aof优先用于重建

RDB

工作原理

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

优点

  • rdb快照是一个简洁的单文件,保存了某个时间点的redis数据集
  • rdb适合做灾备,单个rdb文件能很方便的进行传输
  • 性能好。因为进行持久化时,主进程会fork一个子进程出来,然后把持久化的工作交给子进程,自己不会有相关的IO操作
  • 在数据量较大的情况下,rdb启动得更快

缺点

  • 很容易造成数据的丢失,假设每5min保存一次,出现意外的话,我们可能丢失4min59s的数据
  • rdb使用fork产生子进程进行数据的持久化,如果数据比较大的话可能会花费点时间,造成redis停止服务几毫秒,如果数据量很大且cpu性能不是很好的时候,停止服务的时间甚至会到1s

rdb的启用和禁用

使Redis如果在每N秒后数据发生了M次改变就自动保存快照文件。

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

禁用快照保存功能

save ""
其他的自动触发rdb的方式
  • 从节点全量复制时,主节点发送rdb给从节点完成复制操作,主节点会触发bgsave
  • 执行debug reload时
  • 执行shutdown时,如果没有开启aof,也会触发

如果rdb时发生错误

默认情况下,如果redis在后台rdb时失败,Redis则会停止接受更新操作,这样会让用户了解到数据没有被正确的存储到磁盘上。否则没人会注意到这个问题,可能会造成灾难。 如果后台存储(持久化)操作进程再次工作,Redis会自动允许更新操作。然而,如果你已经恰当的配置了对Redis服务器的监视和备份,你也许想关掉这项功能。如此一来即使后台保存操作出错,redis也仍然可以继续像平常一样工作。

stop-writes-on-bgsave-error yes

数据压缩配置

默认redis会采用lzf(一种压缩算法)对数据进行压缩,禁用掉可以节省cpu

rdbcompression yes

数据校验配置

从版本5的RDB的开始,一个CRC64的校验码会放在文件的末尾。这样更能保证文件的完整性,但是在保存或者加载文件时会损失一定的性能(大概10%)。如果想追求更高的性能,可以把它禁用掉,这样文件在写入校验码时会用0替代,加载的时候看到0就会直接跳过校验。

rdbchecksum yes

手动生成快照

有两个命令

SAVE

SAVE会使用同步的方式生成RDB快照文件,这意味着在这个过程中会阻塞所有其他客户端的请求,直到持久化完成。因此不建议在生产环境使用,除非因为某种原因需要去阻止redis使用子进程进行后台生成快照。

BGSAVE

BGSAVE命令该触发方式会fork一个子进程,由子进程负责持久化过程,因此阻塞只会发生在fork子进程的时候。而我们可以使用LASTSAVE命令查看操作的结果。

127.0.0.1:6379> BGSAVE
Background saving started
127.0.0.1:6379> LASTSAVE
(integer) 1433936394

配置文件里禁用了快照生成功能不影响SAVE和BGSAVE命令的效果

BGSAVE的原理

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

关于fork操作

  • 同步操作 ,fork 是一个同步操作
  • 当执行一个 bgsave 或 bgrewriteaof 首先会执行一个 fork 操作,它只是做一个内存页的拷贝,并不是拷贝所有内存,所以他的速度是非常快的。
  • 当 fork操作比较慢或卡在某一个点,这时它会阻塞redis主线程
  • 与内存量息息相关:内存越大,消耗越长(与机器类型有关)
  • info:latest_fork_usec – 可以查看 fork 的执行时间

改善fork

  • 优先使用物理机或者高效支持fork操作的虚拟化技术
  • 控制Redis 实例最大可用内存:maxmemory
  • 合理配置Linux内存分配策略:vm.overcommit_memory = 1
  • 默认是0,当发现没有足够内存做内存分配时,就会不去分配。对于fork讲,会造成fork阻塞。
  • 降低fork频率:例如放宽AOF重写自动触发机制,不必要的全量复制
    —— 常见的持久化开发运维问题总之就是数据量越大fork卡顿的时间就会越长

AOF

由于rdb容易丢失最新的数据,所以aof提供了一种更可靠的持久化方式。每当redis接受到修改命令时,就会把命令追加到aof文件里去,当重启redis服务时,aof里的命令会被重新执行一次,重建数据。

工作过程

  1. 命令传播:Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程序中。
  2. 缓存追加:AOF 程序根据接收到的命令数据,将命令转换为网络通讯协议的格式,然后将协议内容追加到服务器的 AOF 缓存中。
  3. 文件写入和保存:AOF 缓存中的内容被写入到 AOF 文件末尾。如果设定的 AOF 保存条件被满足的话, fsync 函数或者 fdatasync 函数会被调用,将写入的内容真正地保存到磁盘中。

优点

  • 更可靠。有三种模式不进行fsync(fsync是指把缓存中的写指令记录到磁盘中) ,每秒一次fsync,每次修改进行fsync。一般用每秒一次的,意味着你最多丢失一秒钟的数据。
  • aof日志文件是一个纯追加的文件。不会出现日志损坏问题,海域redis-check-aof可以进行修复文件
  • aof文件太大时,redis支持后台自动重写。重写是在一个新的文件上进行,同时redis继续往旧的aof文件中追加数据。新文件上会写入能重建当前数据集的最小操作命令集合。重写完毕后,redis会将新的aof文件替换掉旧的aof,然后把数据开始写到新文件上。
  • AOF把操作命令以简单易懂的格式一条接一条的保存在文件里,很容易导出来用于恢复数据。例如我们不小心用FLUSHALL命令把所有数据刷掉了,只要文件没有被重写,我们可以把服务停掉,把最后那条命令删掉,然后重启服务,这样就能把被刷掉的数据恢复回来。

缺点

  • aof文件较大于rdb
  • 每秒一次fsync,每次修改进行fsync策略下,速度慢于rdb

相关的配置

appendony yes ##启用aof
dir ./ ##文件存放目录,与rdb共用
appendfilename ##默认文件名

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

关于appendfsync no的说明

在这种模式下,在写入内存的aof_buf后,redis仅仅将aof_buf写入到aof文件中,但不会调用fsync 函数或者 fdatasync 函数将写入的内容真正地保存到磁盘中。

在这种模式下,真正的保存只会在一下任意一种情况中被执行:

  • redis被关闭
  • aof功能被关闭
  • 系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)

这三种情况都会引起redis主进程阻塞,always的保存功能是由redis主进程执行的,所以也会阻塞。everysec中,保存功能是由子进程进行的,所以不会引起主进程阻塞。

其日志重写原理

当写操作命令不断增加时,aof文件会不断增大,有一种aof文件优化思路是“只需要重建能够恢复当前状态的aof文件即可,也就是说如果有一个递增的计数器到100次,新的aof文件只需要记录其最后的值即可”。

重写流程如下

  • redis首先fork出一个子进程
  • 子进程根据当前数据集把新的aof写到一个临时文件里
  • 主进程持续把新的变动写到内存的buffer里和旧的aof文件里(这样可以保证重写失败数据也不会丢失)
  • 当子进程完成重写后给主进程一个信号,然后把内存里的buffer追加到新的aof里。
  • 替换新旧aof文件

在我看来,主进程将新的变动写到旧的aof里是一个备用手段,这样可能降低了一部分性能,但保证了数据的安全

  • 重写是直接把当前内存的数据生成对应命令,并不需要读取老的AOF文件进行分析、命令合并。
  • 不管是RDB还是AOF都是先写入一个临时文件,然后通过 rename 完成文件的替换工作。

重写相关的命令

# Redis会记住自从上一次重写后AOF文件的大小(如果自Redis启动后还没重写过,则记住启动时使用的AOF文件的大小)。
# 如果当前的文件大小比起记住的那个大小超过指定的百分比,则会触发重写。
# 同时需要设置一个文件大小最小值,只有大于这个值文件才会重写,以防文件很小,但是已经达到百分比的情况。

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 要禁用自动的日志重写功能,我们可以把百分比设置为0:
auto-aof-rewrite-percentage 0

# 手动触发重写
bgrewriteaof

数据损坏修复

如果因为某些原因(例如服务器崩溃)AOF文件损坏了,导致Redis加载不了,可以通过以下步骤进行修复:

  1. 备份AOF文件。
  2. 使用redis-check-aof命令修复原始的AOF文件:
$ redis-check-aof --fix
  1. 可以使用diff -u命令看下两个文件的差异。
  2. 使用修复过的文件重启Redis服务。

从RDB切换到AOF

  • 备份一个最新的dump.rdb的文件,并把备份文件放在一个安全的地方。
  • 运行以下两条命令:
$ redis-cli config set appendonly yes # 打开aof
$ redis-cli config set save "" # 关闭rdb
  • 确保数据跟切换前一致。
  • 确保数据正确的写到AOF文件里。
  • 如果有需要,更改redis.conf中启用aof和关闭rdb,因为命令行方式修改配置在重启Redis后就会失效。

第二条命令是用来禁用RDB的持久化方式,但是这不是必须的,因为你可以同时启用两种持久化方式。

备份

建议的备份方法:

  • 创建一个定时任务,每小时和每天创建一个快照,保存在不同的文件夹里。
  • 定时任务运行时,把太旧的文件进行删除。例如只保留48小时的按小时创建的快照和一到两个月的按天创建的快照。
  • 每天确保一次把快照文件传输到数据中心外的地方进行保存,至少不能保存在Redis服务所在的服务器。

线上性能建议

  • 如果redis数据不是特别重要或者可以通过其他方式重写生成数据,可以关闭持久化
  • 自己制定策略定期检查redis情况,然后可以手动触发备份、重写数据
  • 单机多个实例的话,要防止多个机器同时运行持久化或重写操作,防止出现内存、cpu、io资源竞争,让持久化变为串行
  • 加入主从机器,从从机上进行备份
  • rdb与aof可以同时存在,可以配合使用

参考文章

  • Redis持久化
  • 一文看懂Redis的持久化原理
  • AOF