Redis 日志篇:无畏宕机快速恢复的杀手锏

65 哥别急,「码哥字节」带你一步步深入理解到底 Redis 宕机后如何快速恢复的。

Redis 数据存储在内存中,是否可以考虑将内存中的数据写到磁盘上呢?当 Redis 重启的时候就把保存在磁盘上的数据快速恢复到内存中,这样就能实现重启后正常提供服务了。

65 哥:我想到一个方案,每次执行「写」操作操作内存的同时写入到磁盘

这个方案有一个致命问题:每次写指令不仅写内存还是写入磁盘,磁盘的性能相对内存太慢,会导致 Redis 性能大大降低。

内存快照

65 哥:那如何规避这个同时写入的问题呢?

我们通常将 Redis 当作缓存使用,所以即使 Redis 没有保存全部数据,还可以通过数据库获取,所以 Redis 不会保存所有的数据, Redis 的数据持久化使用了「RDB 数据快照」的方式来实现宕机快速恢复。

65 哥:那什么是 RDB 内存快照呢?

在 Redis 执行「写」指令过程中,内存数据会一直变化。所谓的内存快照,指的就是 Redis 内存中的数据在某一刻的状态数据。

好比时间定格在某一刻,当我们拍照的,通过照片就能把某一刻的瞬间画面完全记录下来。

Redis 跟这个类似,就是把某一刻的数据以文件的形式拍下来,写到磁盘上。这个快照文件叫做 RDB 文件,RDB 就是 Redis DataBase 的缩写。

Redis 通过定时执行 RDB 内存快照,这样就不必每次执行「写」指令都写磁盘,只需要在执行内存快照的时候写磁盘。既保证了唯快不破,还实现了持久化,宕机快速恢复。

RDB内存快照

在做数据恢复时,直接将 RDB 文件读入内存完成恢复。

65 哥:对哪些数据做快照呢?或者多久做一次快照呢?这个会影响快照的执行效率。

65 哥不错呀,开始考虑数据效率问题了。在《Redis 核心篇:唯快不破的秘密》中我们知道他的单线程模型决定了我们要尽可能的避免会阻塞主线程的操作,避免 RDB 文件生成阻塞主线程。

生成 RDB 策略

Redis 提供了两个指令用于生成 RDB 文件:

  • save:主线程执行,会阻塞;

  • bgsave:调用 glibc 的函数fork产生一个子进程用于写入 RDB 文件,快照持久化完全交给子进程来处理,父进程继续处理客户端请求,生成 RDB 文件的默认配置。

65 哥:那在对内存数据做「快照」的时候,内存数据还能修改么?也就是写指令能否正常处理?

首先我们要明确一点,避免阻塞和 RDB 文件生成期间能处理写操作不是一回事。虽然主线程没有阻塞,到那时为了保证快照的数据的一致性,只能处理读操作,不能修改正在执行快照的数据。

很明显,为了生成 RDB 而暂停写操作,Redis 是不答应的。

65 哥:那 Redis 如何实现一边处理写请求,同时生成 RDB 文件呢?

Redis 使用操作系统的多进程写时复制技术 COW(Copy On Write) 来实现快照持久化,这个机制很有意思,也很少人知道。多进程 COW 也是鉴定程序员知识广度的一个重要指标。

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

子进程刚刚产生时,它和父进程共享内存里面的代码段和数据段。这时你可以将父子进程想像成一个连体婴儿,共享身体。

这是 Linux 操作系统的机制,为了节约内存资源,所以尽可能让它们共享起来。在进程分离的一瞬间,内存的增长几乎没有明显变化。

bgsave 子进程可以共享主线程的所有内存数据,读取主线程的数据并写入到 RDB 文件。

在执行 SAVE 命令或者BGSAVE命令创建一个新的 RDB 文件时,程序会对数据库中的键进行检查,已过期的键不会被保存到新创建的 RDB 文件中。

当主线程执行写指令修改数据的时候,这个数据就会复制一份副本, bgsave 子进程读取这个副本数据写到 RDB 文件,所以主线程就可以直接修改原来的数据。

写时复制技术保证快照期间数据客修改

这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。

Redis 会使用 bgsave 对当前内存中的所有数据做快照,这个操作是子进程在后台完成的,这就允许主线程同时可以修改数据。

65 哥:那可以每秒都执行 RDB 文件么,这样即使发生宕机最多丢失 1 秒的数据。

过于频繁的执行全量数据快照,有两个严重性能开销:

  1. 频繁生成 RDB 文件写入磁盘,磁盘压力过大。会出现上一个 RDB 还未执行完,下一个又开始生成,陷入死循环。

  2. fork 出 bgsave 子进程会阻塞主线程,主线程的内存越大,阻塞时间越长。

优缺点

快照的恢复速度快,但是生成 RDB 文件频率不好把握,频率过低宕机丢失的数据就会比较多;太快,又会消耗额外开销。

RDB 采用二进制 + 数据压缩的方式写磁盘,文件体积小,数据恢复速度快。

Redis 除了 RDB 全量快照以外,还设计了 AOF 写后日志,接下来我们一起来聊下什么是 AOF 日志。

AOF 写后日志,避免宕机数据丢失


AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录。

假设 AOF 日志记录了自 Redis 实例创建以来所有的修改性指令序列,那么就可以通过对一个空的 Redis 实例顺序执行所有的指令,也就是「重放」,来恢复 Redis 当前实例的内存数据结构的状态。

写前与写后日志对比

写前日志(Write Ahead Log, WAL): 在实际写数据之前,将修改的数据写到日志文件中,故障恢复得以保证。

比如 MySQL Innodb 存储引擎 中的 redo log(重做日志)便是记录修改的数据日志,在实际修改数据前先记录修改日志在执行修改数据。

写后日志: 先执行「写」指令请求,将数据写入内存,再记录日志。

AOF写后指令

日志格式

当 Redis 接受到 「set key MageByte」命令将数据写到内存后,Redis 会按照如下格式写入 AOF 文件。

  • 「*3」:表示当前指令分为三个部分,每个部分都是 「$ + 数字」开头,紧跟后面是该部分具体的「指令、键、值」。

  • 「数字」:表示这部分的命令、键、值多占用的字节大小。比如 「$3」表示这部分包含 3 个字节,也就是 「set」指令。

AOF 日志格式

65 哥:为什么 Redis 使用写后日志这种方式呢?

写后日志避免了额外的检查开销,不需要对执行的命令进行语法检查。如果使用写前日志的话,就需要先检查语法是否有误,否则日志记录了错误的命令,在使用日志恢复的时候就会出错。

另外,写后才记录日志,不会阻塞当前的「写」指令执行。

65 哥:那有了 AOF 就万无一失了么?

傻孩子,可没这么简单。假如 Redis 刚执行完指令,还没记录日志宕机了,就有可能丢失这个命令相关的数据。

还有,AOF 避免了当前命令的阻塞,但是可能会给下一个命令带来阻塞的风险。AOF 日志是主线程执行,将日志写入磁盘过程中,如果磁盘压力大就会导致写磁盘很慢,导致后续的「写」指令阻塞。

发现了没,这两个问题与磁盘写回有关,如果能合理的控制「写」指令执行完后 AOF 日志写回磁盘的时机,问题就迎刃而解。

写回策略

为了提高文件的写入效率,当用户调用 write 函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面。

这种做法虽然提高了效率,但也为写入数据带来了安全问题,因为如果计算机发生停机,那么保存在内存缓冲区里面的写入数据将会丢失。

为此,系统提供了fsyncfdatasync两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。

Redis 提供的 AOF 配置项appendfsync写回策略直接决定 AOF 持久化功能的效率和安全性。

  • always:同步写回,写指令执行完毕立马将 aof_buf缓冲区中的内容刷写到 AOF 文件。

  • everysec:每秒写回,写指令执行完,日志只会写到 AOF 文件缓冲区,每隔一秒就把缓冲区内容同步到磁盘。

  • no: 操作系统控制,写执行执行完毕,把日志写到 AOF 文件内存缓冲区,由操作系统决定何时刷写到磁盘。

没有两全其美的策略,我们需要在性能和可靠性上做一个取舍。

always同步写回可以做到数据不丢失,但是每个「写」指令都需要写入磁盘,性能最差。

everysec每秒写回,避免了同步写回的性能开销,发生宕机可能有一秒位写入磁盘的数据丢失,在性能和可靠性之间做了折中。

no操作系统控制,执行写指令后就写入 AOF 文件缓冲就可以执行后续的「写」指令,性能最好,但是有可能丢失很多的数据。

65 哥:那我该如何选择策略呢?

我们可以根据系统对高性能和高可靠性的要求,来选择写回策略。总结一下:想要获得高性能,就选择 No 策略;如果想要得到高可靠性保证,就选择 Always 策略;如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择 Everysec 策略。

优缺点

优点:执行成功才记录日志,避免了指令语法检查开销。同时,不会阻塞当前「写」指令。

缺点:由于 AOF 记录的是一个个指令内容,具体格式请看上面的日志格式。故障恢复的时候需要执行每一个指令,如果日志文件太大,整个恢复过程就会非常缓慢。

另外文件系统对文件大小也有限制,不能保存过大文件,文件变大,追加效率也会变低。

日志过大:AOF 重写机制

65 哥:AOF 日志文件过大着怎么办?

AOF 写前日志,记录的是每个「写」指令操作。不会像 RDB 全量快照导致性能损耗,但是执行速度没有 RDB 快,同时日志文件过大也会造成性能问题,对于唯快不破的 Redis 这个真男人来说,绝对不能忍受日志过大导致的问题。

所以,Redis 设计了一个杀手锏「AOF 重写机制」,Redis 提供了 bgrewriteaof指令用于对 AOF 日志进行瘦身。

其原理就是开辟一个子进程对内存进行遍历转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中。序列化完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了,瘦身工作就完成了。

65 哥:为啥 AOF 重写机制能缩小日志文件呢?

重写机制有「多变一」功能,将旧日志中的多条指令,在重写后就变成了一条指令。

如下所示:

AOF重写机制(纠错:重写前记录了 3 条指令)

65 哥:重写后 AOF 日志变小,最后把整个数据库最新数据的操作日志刷写到磁盘了。重写会不会阻塞主线程呢?

「码哥」上文说了,AOF 日志是主线程写回的,AOF 重写的过程实际上后台子进程 bgrewriteaof 完成,防止阻塞主线程。

重写过程

和 AOF 日志由主线程写回不同,重写过程是由后台子进程 bgrewriteaof 来完成的,这也是为了避免阻塞主线程,导致数据库性能下降。

总的来说,一共出现 两个日志,一次拷内存数据拷贝,分别是旧的 AOF 日志和新的 AOF 重写日志和 Redis 数据拷贝。

Redis 会将重写过程中的接收到的「写」指令操作同时记录到旧的 AOF 缓冲区和 AOF 重写缓冲区,这样重写日志也保存最新的操作。等到拷贝数据的所有操作记录重写完成后,重写缓冲区记录的最新操作也会写到新的 AOF 文件中。

每次 AOF 重写时,Redis 会先执行一个内存拷贝,用于遍历数据生成重写记录;使用两个日志保证在重写过程中,新写入的数据不会丢失,并且保持数据一致性。

AOF 重写过程

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-imoC8GXC-1714945198087)]

[外链图片转存中…(img-WsXUGyGk-1714945198088)]

[外链图片转存中…(img-dkFyzioE-1714945198088)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 16
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值