Redis持久化


前言

首先要明确一点,redis作为缓存,数据是可以丢的,因为要求的是急速。redis作为数据库的时候,数据是绝对不能丢。
无论是MYSQL或者是oracle还是redis,他们作为存储层,都必须存在两个东西,1个是快照/副本,一个是日志(记录每条操作)。
Redis的持久化可以分为单机自己持久化与主从复制。首先谈谈单机自己的持久化方法。(RDB、AOF、RDB+AOF)三种策略。

RDB持久化

RDB的方式是具有时点性的。它分为2种模式:
1,save:是一种阻塞式的,redis不会对外提供服务。比如关机维护期间。
在这里插入图片描述
2.bgsave:是一种非阻塞的。通过fork创建子进程,但是里面的数据没有拷贝出来(子进程指针引用和父进程引用一样)。如果发生修改,会触发写时复制。一般情况是父进程修改数据或者响应,子进程只负责数据落地。子进程的指针仍然指向原来的物理地址。(虚拟地址到物理地址的映射),所以子进程看不到数据的变化,父进程修改对子进程不可见。子进程只负责写的复制操作。所以落的数据才是真正的时点数据。
这里有个写时复制的概念,通俗来讲,子进程在落数据时,父进程接受到了改变的命令,此时只是改的父进程的指针。’
这里解决了到非阻塞的两大问题,一个是解决速度与内存的占用,通过fork,将指针带过去。
一个是解决独立的问题,通过写时复制,父进程只修改自己的指针。
在这里插入图片描述
在这里插入图片描述
过于频繁的执行全量数据快照,有两个严重性能开销:
1.频繁生成 RDB 文件写入磁盘,磁盘压力过大。会出现上一个 RDB 还未执行完,下一个又开始生成,陷入死循环。
2.fork 出 bgsave 子进程会阻塞主线程,主线程的内存越大,阻塞时间越长。

但是RDB也有一些弊端。
1.不支持拉链,只有一个dump.rdb,不会形成一个拉链,保存着每一天的dump.rdb。所以需要人为的把dump.rdb拉出来。
2.丢失的数据会相对多一些,时点与时点的窗口数据容易丢失,比如8点得到rdb,9刚要得到一个rdb的时候,挂机了。
3.不支持实时持久化,因为bgsave每次运行都需要fork操作创建子进程,属于重量级操作。

RDB的优点
存的是二进制文件,适合全量复制的场景,恢复的速度相对较快。
和 AOF 不同的是 RDB 保存的是数据而不是操作,在进行数据恢复的时候,直接把 RDB 的文件读入到内存,即可完成数据恢复。
在这里插入图片描述

AOF持久化与混合策略

简介

redis的AOF日志存储的是一条条命令,比如set k1 xxxx这样的命令。但是真正存到appendonly.aof中的时候,会多一些东西。这里的*2代表一共有2个字段,分别为select与0,而$6代表该字段一共有6位.从下图也可清晰看到AOF方式存储的是纯文本文件。
在这里插入图片描述
在配置中,默认情况下aof是关闭的,所以需要用的时候要开启:appendonly yes。RDB与AOF可以同时开启,但是如果开启了AOF,只会用AOF恢复。在4.0之后,AOF中包含了RDB的全量,AOF只是增加当时的新记录写操作,具体在重写方式中说。AOF所有的写入命令都会加入到aof_buf(缓冲区)中,缓冲区再根据对应的策略向磁盘做同步的操作。为什么不直接写入到磁盘?因为如果每次写AOF文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负载。

AOF相比RDB而言,丢失数据相对少一些,默认是1秒时,存储数据。

写后日志与写回策略

写后日志
AOF是写后日志,相比MYSQL的写前日志是利用redo log(重做日志)记录修改的数据日志,在实际修改数据前先记录修改日志在执行修改数据。而写后日志先执行写指令请求,将数据写入内存,再记录日志。所以AOF日志对数据库命令的保存顺序是,Redis 先执行命令,把数据写入内存,然后才记录日志。
为什么Redis用了写后日志呢?写后日志避免了额外的检查开销,不需要对执行的命令进行语法检查。如果使用写前日志的话,就需要先检查语法是否有误,否则日志记录了错误的命令,在使用日志恢复的时候就会出错。
IO写回策略
原因:1.假如 Redis 刚执行完指令,还没记录日志宕机了,就有可能丢失这个命令相关的数据。
2.AOF 避免了当前命令的阻塞,但是可能会给下一个命令带来阻塞的风险。AOF 日志是主线程执行,将日志写入磁盘过程中,如果磁盘压力大就会导致写磁盘很慢,导致后续的「写」指令阻塞。
所以事实上,需要自己定义写回方式,数据并不是从缓存直接写入到磁盘中,而是通过写入到缓冲区,再根据不同的写回策略,刷进磁盘。

三个IO级别,
NO : 不调用flush刷进磁盘,由操作系统控制何时刷进磁盘,一般是缓冲区满了再刷。
EVERYSEC:每秒钟调用flush刷进磁盘。(默认)
Always: 数据最可靠的,redis直接向buffer充数据,再写磁盘(也就是写入到AOF文件)。最多丢失一条。

所以,要获得高性能,就选择 No 策略;如果想要得到高可靠性保证,就选择 Always 策略;如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择 Everysec 策略。

重写机制

为什么有重写机制呢?
假设有这样的情景:redis运行了10年,而且开启了AOF,但是再第11年的时候,redis挂了,AOF会很大,估约10T,如果进行恢复,那么时间成本很大(5年)。
对于上述的问题可以有这样的解决思路:如果能保持住AOF的优点,而且让AOF足够小,那么问题也就解决了。所以就引入了重写这个概念。重写利用bgrewriteaof实现了子进程重写功能,时间复杂度为(O(n),键值对总量)
不过,使用子进程也有一个问题需要解决,因为子进程在进行AOF重写期间,服务器进程还需要继续处理命令请求,而新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的AOF文件所保存的数据库状态不一致。
所以在以有了AOF缓冲区后,再加入了一个AOF重写缓冲区,这个重写缓冲区仅仅在创建子进程后使用,此时,父进程执行了命令,都会将两者的命令同时写入到AOF缓冲区(aof_buf)与AOF重写缓冲区。
当子进程完成重写操作后,会发送信号给父进程,父进程调用一个信号函数,并会执行以下2步:

  1. 将AOF重写缓冲区内容写入到新的AOF文件中,这时新的AOF文件与此时的父进程改变的数据一致。
  2. 对老的AOF文件覆盖。

但是此时父进程会阻塞等待子进程完成这两个任务(调用了信号函数),当完成了这些后,父进程继续可以接受请求了。

什么时候会触发AOF的后台重写机制呢?可以手动调用bgwriteaof触发。

在redis的4,0之前,重写是通过删除抵消的命令并合并重复的命令进行重写的,但最终还是生成的是一个纯指令的文件。比如set k1 xxx 到最后set k1 ooo 只会保留最后的set k1 ooo;

但是在4.0之后,持久化开始时,将老的数据RDB的方式到AOF文件中去;在持久化过程中,以AOF增量的方式Append到AOF文件。所以此时AOF是个混合体。
《开启方式aof-use-rdb-preamble yes》
这样有一个优点::服务器既可以通过AOF文件包含的RDB数据来实现快速的数据恢复操作,又可以通过AOF文件包含的AOF数据来将丢失数据的时间窗口限制在1s之内。

RDB与AOF的比较

RDB文件优点

  • RDB是一个二进制文件,所以占的空间比较小,非常适用于全量备份的场景。
  • RDB加载文件时通过数据加载到内存,比AOF方式再执行命令的方式快很多。

RDB文件缺点

  • RDB没办法达到秒级持久化,因为bgsave每次运行都会fork出一个子进程,频繁操作成本很高。
  • 时间适用于几个小时备份一次

AOF文件优点

  • AOF持久化可以达到秒级持久化,安全性比RDB高了些。

AOF文件缺点

  • AOF由于本身存的时命令,所以是纯文本文件,所以体积比AOF大了很多,并且生成AOF时间比RDB长。
  • AOF恢复操作远远比RDB恢复操作执行速度满了很多。(一个命令,一个直接读入内存)
  • AOF如果发生了重写,也会调用bgrewiteaof命令,但是也会调用fork子进程,所以在体积较大的情况,进程AOF会占有大量资源,而且导致服务器阻塞。
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐