Redis如何保证挂掉之后重启数据不丢失

我们都知道 redis 是一个开源的、基于内存的数据结构存储器,可以用作数据库、缓存和消息中间件。最常用做缓存。但是存在内存里面的东西是不稳定的,因为如果系统挂掉重启的话,内存里面数据都会丢失,那么在实际应用中,如果我们用作缓存,应该怎样保证redis挂掉重启后,数据恢复,任然可用呢? 这是一件很重要的事情。接下来了解一下 Redis 中的 持久化机制。究其 意义、实现方式、以及原理。

Redis 持久化机制

持久化的意义:发生故障恢复数据,保障可用性

redis 持久化主要是:RDB、AOF 两种;

RDB: 将数据库的快照以二进制的方式保存到磁盘;

AOF: 将所有写入命令及相关参数以协议文本的方式写入文件并持久保存磁盘。

RDB (快照持久化)

通过创建快照获取存储在内存中的数据在某个时间点的副本,然后对快照进行备份,可以将快照复制到其他服务器作为副本(主从架构),可以将快照存储到本地文件,用于重启恢复。主要有两个参数构成:时间和改动的键值的个数,即当在指定时间内被更改的键的个数大于执行数值时,就会进行快照。就像拍照一样,将这一瞬间的所有东西都保存下来。

RDB 是 Redis 默认采用的持久化方式,在 redis.conf 配置文件中。

规则:

rdb文件路径:

默认rdb文件存放路径是当前目录,文件名是:dump.rdb。

可以在配置文件中修改路径和文件名,分别是dir 和 dbfilename

当 Redis 启动后,会读取 rdb 快照文件,将数据从硬盘加载到内存。

快照流程

满足配置规则条件时,

1、redis 调用 fork 函数 创建 持久化 子进程;

2、子进程 将 指定数据集 从 内存 写入到 临时的 rdb 快照文件中;

3、子进程 完成 写入后,redis 使用这个新创建的临时 rdb 文件 替换原来的 rdb 文件(删除旧文件);

也就是说任何时候RDB文件都是完整的。

这使得我们可以通过定时备份RDB文件来实 现Redis数据库备份。

RDB文件是经过压缩(可以配置rdbcompression参数以禁用压缩节省CPU占用)的二进制格式,所以占用的空间会小于内存中的数据大小,更加利于传输。

除了自动快照,也可以手动调用 save 或者 bgsave 命令,两个命令的区别在于,前者是由主进程进行快照操作,会阻塞住其他请求,后者会通过 fork 子进程进行快照操作。

注:新的 rdb 文件存储的是执行 fork 那一刻的内存数据。

  • 优点

    1.RDB 是一个非常紧凑(compact)的文件,它保存了redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复(将持久化到硬盘中的文件恢复即可)。

    2.生成 RDB 文件 的时候,redis 主进程会 fork() 一个子进程来处理所有保存工作,主进程不需要进行任何磁盘 IO 操作。

    3.RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

  • 缺点

    1.每次快照是保存整个数据集数据,可能触发快照时间比较长,比如 10 分钟进行一次,那么如果期间系统挂掉,就有几分钟数据丢掉。最后一次持久化后的数据可能丢失。

    2.每次保存rdb快照文件,都需要 fork 一个子进程处理持久化工作,如果数据量庞大,可能非常耗时,造成服务器紧张,然后停止一段时间给客户端服务;

AOF

AOF 持久化,默认是关闭的

配置

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

  • 打开 AOF 持久化机制之后,redis 每次接收到一条写命令,就会写入日志文件中,先写入系统缓存,然后每隔一定时间再 fsync 一下

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

AOF持久化配置文件的名称:

  appendfilename “appendonly.aof”

AOF 持久化策略(默认每秒 everysec):

1
2
3
4
5
6

appendfsync always # (同步持久化,每次发生数据变更会被立即记录到磁盘,性能差但数据完整性比较好)

appendfsync everysec # (异步操作,每秒记录,如果一秒钟内宕机,有数据丢失)

appendfsync no #(将缓存回写的策略交给系统,linux 默认是30秒将缓冲区的数据回写硬盘的)

always: 每次写入一条数据,立即将这个数据对应的写日志fsync到磁盘上去,性能非常非常差,吞吐量很低; 确保说redis里的数据一条都不丢

AOF rewrite 策略

AOF持久化机制存在一个致命的问题,随着时间推移,AOF文件会膨胀,如果频繁写入AOF文件会膨胀到无限大,当server重启时严重影响数据库还原时间,影响系统可用性。为解决此问题,系统需要定期重写AOF文件,目前采用的方式是创建一个新的AOF文件,将数据库里的全部数据转换成协议的方式保存到文件中,通过此操作达到减少AOF文件大小的目的,重写后的大小一定是小于等于旧AOF文件的大小。

配置:

1
2
3
auto-aof-rewrite-percentage 100 # 当前写入日志文件的大小超过上一次rewrite之后的文件大小的百分之100 时就是2倍时触发Rewrite)

auto-aof-rewrite-min-size 64mb
重写AOF提供两种方式
REWRITE: 在主线程中重写AOF,会阻塞工作线程,在生产环境中很少使用,处于废弃状态;

BGREWRITE: 在后台(子进程)重写AOF, 不会阻塞工作线程,能正常服务,此方法最常用。
BGREWRITE 流程
1.收到BGREWRITE命令或者系统触发AOF重写

2.主进创建一个子进程并进行AOF重写

3.主进程异步等待子进程结束(信号量),此时主进程能正常接收处理用户请求

注:在处理过程中,用户请求会修改数据库里数据,会使得当前数据库的数据跟重写后 AOF 里不一致,需要有种机制保证数据的一致性。当前的做法是在重写 AOF 期间,系统会新开一块内存用于缓存重写期间收到的命令,在重写完成以后再将缓存中的数据追加到新的AOF。在处理命令时既要将命令追加到 aof_buf,也要追加到重写AOF Buffer。

AOF流程

1、redis fork 一个子进程

2、子进程基于当前内存中的数据,构建日志,开始往一个新的临时的AOF文件中写入日志

3、redis主进程,接收到client新的写操作之后,在内存中写入日志,同时新的日志也继续写入旧的AOF文件

4、子进程写完新的日志文件之后,redis主进程将内存中的新日志再次追加到新的AOF文件中

5、用新的日志文件替换掉旧的日志文件

AOF配置文件损坏修复方法:

进入redis安装路径 执行

1
redis-check-aof –fix AOF配置文件名称
  • 优点

    1.更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据

    2.日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高

    3.日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite log的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可。

    4.日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。

  • 缺点

    1.对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大

    2.AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的

    3.较为复杂的基于命令日志/merge/回放的方式,比基于RDB每次持久化一份完整的数据快照文件的方式,更加脆弱一些

感谢修正与补充

woodenrobot:https://woodenrobot.me/

1、Redis 默认不开启持久化

2、
a: 在做RDB的时候 key 有了改动会出现什么情况,即一个持续写入的数据库如何生成快照呢?(写时复制技术)
b: 服务器内存 4 G ,Redis 占了 3 G ,在 RDB 模式下有没有问题?(fork持久化使用数据指针,并不会全量拷贝内存,没问题)

    (1) Copy On Write (写时复制): 

    linux中引入了“写时复制“技术,
    也就是只有进程空间的各段的内容要发生变化时,
    才会将父进程的内容复制一份给子进程。

    内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟究竟结构,
    但是不为这些段分配物理内存,它们共享父进程的物理空间,
    当父进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。

    使用了“引用计数”,会有一个变量用于保存引用的数量。
    当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,
    当有类析构时,这个计数会减一,直到最后一个类析构时,此时的引用计数为1或是0,此时,程序才会真正的Free这块从堆上分配的内存。引用计数就是string类中写时才拷贝的原理!

    (2) 什么情况下触发Copy On Write(写时复制):

    在共享同一块内存的类发生内容改变时,才会发生Copy On Write(写时复制)。比如string类的[]、=、+=、+等,还有一些string类中诸如insert、replace、append等成员函数等,包括类的析构时。

3、AOF追加:并不是发送到Redis的所有命令都要记录到AOF日志里面,只有那些会导致数据发生修改的命令才会追加到AOF文件。

参考:

[1] https://blog.csdn.net/qq_41864967/article/details/90522398

[2] https://yq.aliyun.com/articles/11028

[3] https://www.jianshu.com/p/a91329ae210c

[4] https://blog.csdn.net/doc_sgl/article/details/45129857