一文读懂Redis持久化机制

我们日常开发中,使用Redis的普遍场景就是用作缓存。也就是把后端数据库的数据存储在内存中,然后从内存读取数据,响应速度会非常的快。并且使用缓存还会降低数据库的访问压力。但是这里也有一个绝对不能忽视的问题:一旦服务器宕机,内存中的数据就会全部丢失。

为了保证数据的持久性,Redis提供了两种持久化方案:AOF日志和RDB快照。我们可以根据实际情况,在项目中灵活配置。

下面我们首先来看看AOF日志。

AOF日志

AOF(Append Only File),它记录了Redis收到的每一条命令,并以文本形式保存。

AOF相关配置

Redis默认不开启AOF持久化方式,我们可以修改redis.conf文件配置开启:

# 开启aof机制
appendonly yes

# aof文件名
appendfilename "appendonly.aof"

# 写入策略 默认 everysec
# appendfsync always
appendfsync everysec
# appendfsync no

# 自动重写配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 保存目录
dir ./

AOF是写后日志。跟MySQL的写前日志(WAL)相反。写前日志指的是,在实际写数据前,先把修改的数据记录到日志文件,以便故障时进行恢复。写后的意思是,Redis先执行命令,把数据写入内存,然后再记录日志到磁盘。

看到这里,我们就要想了,为什么AOF要先执行命令,再记日志呢?

Redis为了避免额外的性能开销,再向AOF里面记日志的时候,并不会先去检查命令的语法正确性,而是先让系统执行命令,只有执行成功之后,这条才会被记录下来,否则,系统就会报错。所以写后日志好处之一就是,防止出现错误命令的问题。

此外,另一个好处就是,因为是在命令执行之后,才去记录日志,所以不会阻塞当前的写操作。

当然了写后日志也会带来一定风险。

首先第一个:数据丢失。如果执行完一个命令,还没来得及写日志系统就发生宕机了,此时就会发生数据丢失的风险。

其次,AOF日志是在主线程中执行的,如果在日志写入磁盘的时候,磁盘写压力大,就会导致写盘很慢。我们都知道,redis是单线程的,如果主线程发生阻塞,就导致后续的操作都无法进行

那么如何解决这两个风险呢?

聪明的你应该发现了,这两个风险都跟AOF写回磁盘的时机相关。如果我们能找到一种合适的时机,这两个风险是不是就能避免呢?我们继续看。

AOF的持久化实现

AOF的持久化实现分为三个步骤:

  1. 命令追加:当 AOF 持久化功能被打开时,服务器执行完一个写命令之后,会议协议格式将被执行的命令追加到 aof_buf 缓冲区的末尾;
  2. 文件写入:将 aof_buf 缓冲区的内容写入和保存到 AOF 文件,具体的写回策略由 appendfsync 选项的值来确定;
  3. 文件同步

AOF三种写回策略

在Redis的配置文件中,有这样几个配置:

# appendfsync always
appendfsync everysec
# appendfsync no
  • always:同步写回,每个写命令执行完,立马同步地将日志写回磁盘;
  • everysec:每秒写回,redis默认写回策略,即每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
  • no:由操作系统控制的写回,每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。

我们来总结一下这三种策略

  • always:这种策略很安全,它能基本做到不丢失数据,但是每个写命令之后都有一个落盘的操作,所以它对系统的影响也是最大的;
  • no:这种策略最不安全,因为落盘的时机不在redis手中,一旦发生宕机对应的数据就会丢失;
  • everysec:避免了always策略的性能开销,也降低了no策略的丢失风险,最多可能会丢失1s的数据,它算是在二者之间取了个折中。

三种我们策略应该如何选择呢

  • 如果想要系统的高性能选 no策略;
  • 如果想要高可靠性就选择 always策略;
  • 如果二者兼容的话只有 everysec策略啦。

注意,到这里没有结束哦。我们虽然按照系统的需求选择了写回策略,但是AOF是以文件的形式记录接收到的命令的,这时候随着写入命令的不断增加,AOF文件的体积会变得越来越大。

如果AOF太大,再往里面追加命令的时候,效率就会降低。而一旦发生宕机,用AOF恢复的速度也会非常慢。

为了避免这种问题,接下来继续AOF的重写机制。

AOF重写机制

简单点说就是根据原有的AOF文件,重新创建一个新的AOF文件,只不过这个新的AOF文件比原来的更小。

那么Redis是怎么把文件变小的呢?

原来,Redis的重写机制具有多变一的功能,也就是检查数据库的键值对,记录下键值对的最终状态,从而实现对某个键值对多次操作产生的多条命令压缩为一条的效果。

我们知道,AOF文件是以追加的方式,记录接收到的命令。当对一个键值对反复修改时,就会记录多条命令。然而在重写的时候只是记录下了当前的最新状态,这样就实现了多变一。

看到这,大家可能会问了,既然redis是单线程的,它既要执行写入命令,同时又要同步日志到磁盘,这里又冒出来一个重写机制,但是它响应的速度依然很快,这到底是咋回事呢?

Redis为了避免阻塞主线程,导致数据库性能下降。就会创建一个子线程—bgrewriteaof由子线程完成重写过程

重写过程:

  1. 首先,主线程fork出bgrewriteaof子线程;同时也会把主线程的内存拷贝一份给bgrewriteaof子线程,这里的拷贝指的是子进程复制了父进程页表,此时子线程可以共享访问父进程的内存数据了;
  2. 然后,子线程就可以将新的内容记入重写日志了;注意,是重写日志!!!
  3. 对于新的操作命令,继续父线程处理,redis会把这个操作记录到正在使用的AOF日志的缓冲区,这样一来就不用担心宕机问题。同样,也会在重写日志的缓冲区也记录一份;
  4. 当子线程完成重写工作之后,缓冲区里的这些新的操作也会记录到新的AOF文件。此时,我们就可以用新的AOF文件替代旧文件了。

重写触发的时机:

  1. 手动触发:手动发送bgrewriteaof指令
  2. 自动触发:涉及到两个配置参数,只有AOF文件大小同时超出下面这两个配置项时,会触发AOF重写
    • auto-aof-rewrite-min-size:AOF重写时文件的最小大小,默认为64MB;
    • auto-aof-rewrite-percentage:重写百分比,当前AOF文件比上一次重写后AOF文件的增量大小,和上一次重写后AOF文件大小的比值。

到这里AOF日志基本就介绍完了,接下来我们继续看看另一种持久化方法:内存快照

RDB

RDB(Redis DataBase)内存快照,是redis默认的持久化方式。具体就是将某一时刻的内存数据以文件的形式保存到磁盘上

请注意,这里是保存的是数据!!! 不是操作。所以,在数据恢复的时候,我们就可以直接把RDB文件读入内存,快速完成恢复。

RDB相关配置

# 备份的频率:900秒内至少一个键被更改则进行快照
save 900 1
save 300 10
save 60 10000

# 快照创建出错后,是否继续执行写命令
stop-writes-on-bgsave-error yes

# 是否对快照文件进行压缩
rdbcompression yes

# 文件名称
dbfilename dump.rdb

# 文件保存位置
dir ./

RDB持久化流程

首先我们要确认的是,我们在给内存数据做快照的时候,做的是全量快照,因为我们的数据都在内存中,为了确保可靠性,就必须把内存中的所有数据都记录到磁盘中。

Redis给我们提供了两个命令来创建快照:分别是 savebgsave

  • save :在主线程中执行,会导致阻塞;
  • bgsave:bgsave命令会fork一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 redis RDB 文件生成的默认配置

这个时候,我们就可以通过bgsave命令来执行全量快照,这样既提供了数据的可靠性保证,同时也避免了对redis的性能影响。

接下来,我们需要关注一个问题。在对内存数据做快照时,这些数据还能被修改吗?

如果能修改,意味着Redis还能正常处理写操作,否则的话,就要等所有快照写完才能执行,这会大大降低性能。

这里我们先给出答案:在对内存做快照时,这些数据肯定还是可以被修改的。
RDB采用写时复制(COW,copy on write)策略。在执行快照的同时,正常处理写操作。

简单来说,Redis 在持久化时会调用glibc的函数fork,产生一个子进程,快照持久化此时就交给子进程来处理,父进程则继续处理客户端请求。

子进程在做持久化的时候,不会对现有的内存数据结构进行修改,它只是进行遍历读取,然后序列化写到磁盘中。但是父进程不一样,它必须持续接受客户端请求,然后对内存数据结构进行修改。

如下图所示:如果主线程对数据是读操作,那么,主线程和子进程相互不影响。如果主线程要修改一块数据,那么这块数据就会被复制一份,生成该数据的副本。然后主线程对这个副本进行修改。
在这里插入图片描述
这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。

快照的频率

为了提高系统的可靠性,防止宕机导致的数据丢失,我们肯定希望快照的时间越短越好。我们可能会想,通过bgsave子线程来执行快照,这样既不会阻塞主线程,同时也尽可能地少丢失数据。但是这样真的是完美的吗?

答案是否定的。虽然 bgsave 执行时不阻塞主线程,但是如果频繁的执行全量快照也会有两方面的开销

  • 频繁将全量数据写入磁盘,会给磁盘带来很大压力;
  • bgsave 子进程需要通过 fork 操作从主线程创建出来,虽然子进程在创建之后不会阻塞主线程,但是在fork的时候本身就会阻塞主线程,如果频繁的调用fork创建子进程,就会频繁的阻塞主线程了。

那我们该如何处理呢?

此时,我们可以做增量快照,也就是,在做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。

但是,这么做的前提是,我们需要记住哪些数据被修改了。这会带来额外的空间开销问题。

两种持久化方式对比

AOF每次记录的是操作命令,一般需要持久化的数据量不大。只要不是设置的always方式,对性能不会造成太大影响。但是在数据恢复时,需要把所有的命令都执行一遍。如果操作日志很多,redis恢复的速度就会很慢,可能会影响到正常使用。

而RDB快照的方式就弥补了这一点,它每次记录的是数据,redis在故障恢复的时候速度就会很快。但是,RDB的问题是,它执行快照的频率不好控制,如果频率太快会对系统带来性能影响,如果频率太慢就会造成更多的数据丢失。
那么,有没有方法既能利用 RDB 的快速恢复,又能以较小的开销做到尽量少丢数据呢?

当然有,下面继续Redis 4.0 混合持久化

混合持久化

混合持久化,就是将RDB文件的内容和增量的AOF日志文件存在一起。

简单来说,内存快照是以一定频率执行的,那么在两次快照之间,使用AOF 日志记录发生的增量操作。如下图所示:

在这里插入图片描述
T1 和 T2 时刻的修改,用 AOF 日志记录,等到第二次做全量快照时,就可以清空 AOF 日志,因为此时的修改都已经记录到快照中了,恢复时就不再用日志了。

这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势,提高了数据恢复效率,以及数据的可靠性。

如果你还想看更多优质原创文章,欢迎关注我的公众号「ShawnBlog」。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值