redis持久化

1,154 阅读10分钟

redis持久化意义

redis是内存数据库,数据基本都存放在内存中,面临的问题就是一旦服务器进程退出,服务器中的数据库状态也会消失不见。所以需要将数据保存到磁盘中,甚至是远程云服务中,当需要数据恢复时,可以直接根据备份文件恢复数据。redis对于保存到磁盘的持久化提供了俩中方案:RDB和AOF。

image-20201026205406912

RDB

RDB文件就是定期生成数据的快照文件。当redis进程重新启动时,会自动读取RDB文件实现数据还原。redis默认提供开启RDB文件的持久化方案。

image-20201026204001423

RDB文件生成和载入

RDB文件生成

  1. save:SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求
  2. bgsave: BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求
image-20201026204636493

生成流程

  • redis根据配置自己尝试去生成rdb快照文件
  • fork一个子进程出来
  • 子进程尝试将数据dump到临时的rdb快照文件中
  • 完成rdb快照文件的生成之后,就替换之前的旧的快照文件

每次生成一个新的快照,都会覆盖之前的老快照

RDB文件载入

Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时检测到RDB文件存在,它就会自动载入RDB文件。并且服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。

配置

在redis.config文件中

// 默认配置
save 900 1
save 300 10
save 60 10000

save n m

服务器在n秒之内,对数据库进行了至少m次修改

  1. 可以手动调用save或者bgsave命令,同步或异步执行rdb快照生成。
  2. save可以设置多个,就是多个snapshotting检查点,每到一个检查点,就会去check一下,是否有指定的key数量发生了变更,如果有,就生成一个新的dump.rdb文件。

原理

saveparams

当redis服务启动是时候会自动读取redis.config配置文件信息,将多个配置读取到一个saveparam的数组中。该数组为redisServer结构的saveparams属性对应的值。

image-20201026211205725

dirty和lastsave

  1. dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)。当服务器成功执行一个数据库修改命令之后,程序就会对dirty计数器进行更新:命令修改了多少次数据库,dirty计数器的值就增加多少。
  2. lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。

执行

Redis的服务器周期性操作函数serverCron默认每隔100毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,它的其中一项工作就是检查save选项所设置的保存条件是否已经满足,如果满足的话,就执行BGSAVE命令。

流程:

  1. 计算当前时间和上次执行时间差。
  2. 遍历saveparams属性值数据,当满足其中一个条件执行RDB文件更新。
  3. 更新dirty和lastsave值。

RDB优缺点

优点

  1. RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说Amazon的S3云服务上去,在国内可以是阿里云的ODPS分布式存储上,以预定好的备份策略来定期备份redis中的数据。
  2. RDB对redis对外提供的读写服务,影响非常小,可以让redis保持高性能,因为redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可。
  3. 相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复redis进程,更加快速。

缺点

  1. 如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,那么会丢失最近5分钟的数据。
  2. RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。

AOF

除了RDB持久化功能之外,Redis还提供了AOF(Append Only File)持久化功能.AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态。

AOF持久化实现

对每一条写入命令作为日志,以append-only方式追加。

image-20201026213000128

流程

  1. redis每接受一条命令就会追加日志文件
  2. 将日志文件写入到OS cache缓存中
  3. 通过fsync将OS cache中的数据写入AOF文件

配置

AOF配置

appendonly yes

AOF持久化,默认是关闭的,默认是打开RDB持久化

appendonly yes,可以打开AOF持久化机制,在生产环境里面,一般来说AOF都是要打开的,除非你说随便丢个几分钟的数据也无所谓

AOF和RDB都开启了,redis重启的时候,也是优先通过AOF进行数据恢复的,因为aof数据比较完整

AOF的fsync策略配置

# appendfsync always
appendfsync everysec
# appendfsync no
  1. always: 每次写入一条数据,立即将这个数据对应的写日志fsync到磁盘上去,性能非常非常差,吞吐量很低; 确保说redis里的数据一条都不丢,选择该策略.
  2. everysec: 每秒将os cache中的数据fsync到磁盘,这个最常用的,生产环境一般都这么配置,性能很高,QPS还是可以上万.
  3. no: 仅仅redis负责将数据写入os cache就撒手不管了,然后后面os自己会时不时有自己的策略将数据刷入磁盘,不可控了.

AOF的rewrite

AOF文件是追加写入命令方式实现持久化,随着redis的持续使用,AOF文件会越来越大。面对这个问题的解决方案是AOF文件的rewrite。

rewrite实现

image-20201026225623326

流程

  1. AOF文件膨胀到配置大小界限,触发AOF文件rewrite的命令BGREWEITEAOF。
  2. redis进程fork一个子进程,基于redis当前的内存结构,构建新的AOF文件。
  3. 在构建新的AOF文件与旧的AOF文件没有任何关系,是基于当前redis内存数据结构,并且会对写入命令进程重构,如:多条同一个键的写入重构成一条命令。
  4. 在rewrite期间,redis会继续接受新的写入命令,并把他们缓存在重新缓存区中。
  5. redis主进程收到rewrite写完信号后,把重新缓存池中的数据写入新的AOF文件,并用新的AOF文件替代旧的。删除旧的AOF文件。

rewrite相关配置

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
  1. auto-aof-rewrite-percentage 100:比如说上一次AOFrewrite之后,是128mb然后就会接着128mb继续写AOF的日志,如果发现增长的比例,超过了之前的100%,256mb,就可能会去触发一次rewrite。
  2. auto-aof-rewrite-min-size 64mb:达到第一个100%的条件之后,还要去跟min-size:64mb去比较,256mb > 64mb,才会去触发rewrite

AOF文件破损修复

redis-check-aof --fix命令来修复破损的AOF文件,一般会把有问题的写入命令删除。

实际使用

AOF和RDB

  1. 不要仅仅使用RDB,因为那样会导致你丢失很多数据。
  2. 也不要仅仅使用AOF,因为那样有两个问题,第一,你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快; 第二,RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug
  3. 综合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择; 用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复

注意事项

  1. 默认开始RDB,AOF需要手动开启,俩者都开启,redis重启,数据恢复优先考虑AOF文件。
  2. 如果RDB在执行snapshotting操作,那么redis不会执行AOF rewrite; 如果redis再执行AOF rewrite,那么就不会执行RDB snapshotting。
  3. 如果RDB在执行snapshotting,此时用户执行BGREWRITEAOF命令,那么等RDB快照生成之后,才会去执行AOF rewrite。

配置

  1. 在企业中,RDB的生成策略,用默认的也差不多。save 60 10000:如果你希望尽可能确保说,RDB最多丢1分钟的数据,那么尽量就是每隔1分钟都生成一个快照,低峰期,数据量很少,也没必要。10000->生成RDB,1000->RDB,这个根据你自己的应用和业务的数据量,实际决定。

  2. AOF一定要打开,fsync策略everysec。

  3. auto-aof-rewrite-percentage 100: 就是当前AOF大小膨胀到超过上次100%,上次的两倍。

    auto-aof-rewrite-min-size 64mb: 根据你的数据量来定,16mb,32mb。

数据备份方案

RDB非常适合做冷备,每次生成之后,就不会再有修改了。

  1. 写crontab定时调度脚本去做数据备份
  2. 每小时都copy一份rdb的备份,到一个目录中去,仅仅保留最近48小时的备份
  3. 每天都保留一份当日的rdb的备份,到一个目录中去,仅仅保留最近1个月的备份
  4. 每次copy备份的时候,都把太旧的备份给删了
  5. 每天晚上将当前服务器上所有的数据备份,发送一份到远程的云服务上去

脚本:

/usr/local/redis

每小时copy一次备份,删除48小时前的数据
crontab -e
0 * * * * sh /usr/local/redis/copy/redis_rdb_copy_hourly.sh

redis_rdb_copy_hourly.sh

\#!/bin/sh 
cur_date=`date +%Y%m%d%k`
rm -rf /usr/local/redis/snapshotting/$cur_date
mkdir /usr/local/redis/snapshotting/$cur_date
cp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date
del_date=`date -d -48hour +%Y%m%d%k`
rm -rf /usr/local/redis/snapshotting/$del_date


每天copy一次备份
crontab -e
0 0 * * * sh /usr/local/redis/copy/redis_rdb_copy_daily.sh

redis_rdb_copy_daily.sh


\#!/bin/sh 

cur_date=`date +%Y%m%d`
rm -rf /usr/local/redis/snapshotting/$cur_date
mkdir /usr/local/redis/snapshotting/$cur_date
cp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date
del_date=`date -d -1month +%Y%m%d`
rm -rf /usr/local/redis/snapshotting/$del_date

每天一次将所有数据上传一次到远程的云服务器上去

数据恢复方案

  1. 如果是redis进程挂掉,那么重启redis进程即可,直接基于AOF日志文件恢复数据,最多就丢一秒的数据。
  2. 如果是redis进程所在机器挂掉,那么重启机器后,尝试重启redis进程,尝试直接基于AOF日志文件进行数据恢复。AOF没有破损,也是可以直接基于AOF恢复的。如果AOF文件破损,那么用redis-check-aof fix方式修复破损文件。
  3. 如果redis当前最新的AOF和RDB文件出现了丢失/损坏,那么可以尝试基于该机器上当前的某个最新的RDB数据副本进行数据恢复
  4. 当前最新的AOF和RDB文件都出现了丢失/损坏到无法恢复,一般不是机器的故障,人为找到RDB最新的一份备份,小时级的备份可以了,小时级的肯定是最新的,copy到redis里面去,就可以恢复到某一个小时的数据。

参考:

  1. redis设计与实现
  2. 中华石杉的亿级流量

本文使用 mdnice 排版