Redis 攻略面经(二)-- 关于持久化,你了解多少?

157 阅读10分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第十四天,点击查看活动详情

📣 大家好,我是Zhan,一名个人练习时长一年半的大二后台练习生🏀

📣 这篇文章是复习 Redis 持久化相关技术 的学习笔记📙

📣 如果有不对的地方,欢迎各位指正🙏🏼

📣 与君同舟渡,达岸各自归🌊


👉本篇速览

Redis 是基于内存的数据库,如果 Redis 宕机或者把 Redis 的进程关了,是不是数据全都没了呢?

当然不会,Redis 会把缓存的数据持久化到硬盘,当然,如果把保存缓存数据的文件也删了,建议提桶跑路hhh。

Redis 有两种持久化技术,一个是AOF日志,一个是RDB快照,二者都会用日志文件来记录信息达到备份的效果,其中AOF日志记录的是操作命令,RDB快照记录的是二进制数据,我们将在本文探讨一下具体的细节:


📔 AOF 日志

🌅 工作原理

AOF日志会记录所有的写操作命令,不记录没有意义的读操作命令,我们可以举个例子来看看它究竟记录了什么信息:

例如我们执行命令:set name saber,那么AOF中存储的就是:

*3
$3
set
$4
name
$5
saber

我们来解释一下AOF日志文件存储的信息:首先*3代表该条指令有三段,也就是set、name、saber三段,$3代表该段的字节数,set就是3个字节

那么,是先执行命令然后写入日志文件,还是先写入日志文件再执行呢?

如果是先写入日志文件,再执行

  • 此时假设命令有错误,那么写入日志文件前不进行语法检测,就会出现错误的指令存在于日志文件中。
  • 如果先写入了日志文件,执行命令的过程中服务器宕机了,会导致备份了一条实际没有执行的命令。

如果是先执行命令然后写入日志文件

  • 就不会阻塞当前命令的执行,但是会影响到下一个命令的执行,可能会阻塞下一个命令。
  • 如果命令执行成功了,但是在写入日志文件时,服务器宕机了,那么备份的消息也会少了一条。

综合考量,Redis 选择的策略是先执行命令再写入日志文件,接下来我们来看看从执行命令到记录日志的具体的流程:

具体流程就是:

  1. Redis执行命令,把命令写入server.aof_buf缓冲区
  2. 然后IO调用write() 把命令写到内核缓冲区
  3. 内核缓冲区在没有干涉的情况下,会自行选择时机把文件写入磁盘,这个属于内核态,我们可以通过fsync() 强行写入磁盘

那我们怎么控制或者调用这个fsync() 函数呢,这就不得不提到AOF日志的三种策略

  1. Always:就是我们最开始说的,每执行一条命令就会把数据写入AOF日志,也就是说每次写入AOF文件数据后,就执行fsync() 函数。这样做尽管数据丢失的可能性很小,但是对于后一条指令的阻塞,也就是说会影响主进程的性能。
  2. No:也就是不执行fsync() 函数,那么全靠内核自己决定写入硬盘的时机,如果发生宕机,数据丢失的风向相比之下更大,丢失的数据就可能很多,但是它对于主进程性能的影响就很小
  3. Everysec:算是上述两种方式的折中,每秒执行一次fsync() 函数,那么既不会无法预测操作系统写入磁盘的时机,还能性能高一点。

🛎 日志重写

随着写操作的命令越来越多,AOF日志也会越来越大,那么重启Redis,需要读取AOF日志恢复数据的时间就越来越长,因此就有了AOF日志重写。

AOF日志重写:当AOF日志的大小达到了一个指定的值,就会执行AOF重写,以达到压缩AOF日志的目的。

我们可以举个例子,我们前后对age这个字段修改了两次:set age 11, set age 12,其实真正有作用的只是set age 12,经过AOF重写后留下的也就只有set age 12

虽然是这么讲,但是AOF真正的工作原理是:在执行重写后,会读取age的最新的数据库中的value,然后把一条新的指令set age 12写入新的AOF文件

为什么写入新的AOF文件,而不是在原有的文件中进行修改?

如果重写失败了,那么现有的AOF文件就会被污染,可能永远无法用于恢复使用,因此重写会先重写到新的AOF文件,然后覆盖原有的文件。


⌛ 后台重写

对于一个大日志文件去做重写,如果在主进程中完成,由于这个操作很耗时,将会导致很长时间的阻塞,因此不能放在主进程中。

既然不能放在主进程,那么放在子线程还是子进程呢?

  • 如果放在子线程,多线程之前去操作同一份内存,那么就需要进行加锁来保证数据安全性,也会降低性能
  • 但是如果是子进程,父子进程共享内存数据(下文会讲到如何通过页表共享数据),当有一方操作数据的时候,就会发生写时复制,此时父子进程就拥有了独立的数据副本,再通过缓冲区达到一致性,具体的流程我们下面来讨论

让我们来看看后台重写的具体流程:

  1. 首先主进程在生成子进程的时候,会复制一份主进程的页表给子进程,此时主线程会堵塞,因为它要复制页表,什么是页表呢,我们来看下面这个图:

页表记录了虚拟地址和物理地址的映射关系,也就是说,父进程没有把所有的数据都拷贝一份给子进程,而是二者公用一份数据,这样做能节省物理内存资源

  1. 此时,如果主进程要对物理内存的数据进行修改,就会发生所谓的写时复制,也就是只有在写的时候才会复制,此时主线程也会堵塞,因为要复制一份新的物理内存空间,我们以下面这张图来举例:

原本两个进程的页表记录的信息是一致的,都指向name:zhan,sex:1,age20,并且他们的物理内存的信息也是一样的,但是如果我们在主进程执行了命令:set age 21,此时就会发生写时复制,变为:

此处只会复制主线程修改的物理内存数据,没修改物理内存依然是二者共享的。也就是说父进程会指向name:zhan,sex:1,age:21,子进程指向name:zhan,sex:1,age:20,其中name:zhan,sex:1是公用的物理内存

  1. 既然主进程的数据被修改了,那子进程会不会少一部分的数据呢,为了解决这个数据不一致的问题,Redis设置了一个AOF缓冲区,在后台重写的过程中,Redis执行写命令之后,会同步把写命令写入AOF缓冲区和AOF重写缓冲区
  2. 然后在AOF重写工作完成后,就会给主进程发个信号,主进程就会:
    1. 把AOF重写缓冲区的所有内容追加到AOF文件中
    2. 新的AOF文件进行改名并覆盖原有的AOF文件

📸 RDB 快照

在讲AOF日志的时候,我们就提到了,AOF日志记录的是操作命令,而RDB记录的是二进制数据,相较于AOF日志,恢复数据时它的效率更高,因为它只需要把实际的数据导入即可,不需要像AOF执行操作命令。

🎐 使用方式

如果我们想主动的去生成RDB文件,可以使用这两个命令:

  • save:会在主线程生成RDB文件,会出现的问题就是主线程阻塞
  • bgsave:会创建一个子线程来生成RDB文件,就能避免主线程的阻塞,因此使用命令生成RDB时最好是使用bgsave

当然,除了我们手动生成,我们也可以去配置文件里面去配置自动执行bgsave

save 900 1
save 300 10 
save 60 10000

上述配置表明:

  • 900s内进行了一次数据库的修改就执行备份
  • 300s内进行了十次数据库的修改就执行备份
  • 60s内进行了一万次数据库的修改就执行备份

之所以这么做,是因为RDB的备份是全量快照,也就是说备份整个数据库,如果数据库的量比较大,那么一次备份将会是一个很重的操作。

因此,通常设置至少五分钟备份一次,就会出现一次宕机就会丢失五分钟的数据,这就是RDB快照的缺点

在后台做快照的时候,与AOF日志一样,也会复制一份页表给子进程,主进程对数据进行修改的时候,同样会发生写时复制,但是不会同步给RDB快照,也就是说:快照期间,Redis做的修改是不会放在RDB快照中的


🎯 混合持久化

说了这么多,我们大致也了解到了RDB和AOF的缺陷和优点,就又来到了经典的环节,把二者一起使用,在Redis 4.0就提出了混合使用AOF日志和内存快照,称作混合持久化

它的工作原理是:

  1. 在 AOF 重写日志时,重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件
  2. 然后主线程处理的写命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件
  3. 写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

其实就是说,AOF 日志原本的前半部分由 RDB快照代替了,但是最后缓冲区的那部分命令还是以 AOF日志 的形式存储


💬 总结

本文讲了Redis持久化的两种策略: AOF日志RDB快照,对于AOF日志,我们分别介绍了:AOF日志的组成、三种写回策略以及底层原理、AOF重写的具体流程,对于RDB日志,我们分别介绍了:RDB日志的使用方法、弊端、优点。

其实我们最后发现,AOF日志尽管读取的速度比较慢,但是它丢失数据最多也就丢失一秒的数据,RDB快照尽管读取的速度,也就是恢复数据的速度比较快,但是它一旦丢失数据可能就是五分钟的数据,因此我们建议选择混合持久化这个策略。


🍁 友链


✒写在最后

都看到这里啦~,给个点赞再走呗~,也欢迎各位大佬指正以及补充,在评论区一起交流,共同进步!也欢迎加微信一起交流:Goldfish7710。咱们明天见~