从前有句古话说得好,天将降大任于斯人也,必要先看 Redis。
以前今人还说过,窗前明月光,抬头 Redis。
今人还说过,所有的答案都在源码里。
昨天还有人跟我说,用 Redis 比 Tair 申请要不便。
不识庐山真面目,只缘身在此山中
咱们先给出一副大图,来看看 Redis AOF Rewrite 的总体流程是怎么样的。
先看看大图里的几大组成部分
- 主过程与子过程,大家都晓得,Redis AOF Rewrite 是通过创立一个子过程来实现的。父子过程有一个重要个性,那就是 ” 读时共享,写时复制 ”。前面咱们具体聊
- 父子过程间通信应用的三个通道。
- 客户端写入 Redis 主过程时,波及到的两个数据结构,aof_buf,aof_rewrite_buf_blocks;子过程波及到的 aof_child_diff 数据结构。
- 一份以后应用的 AOF 文件,这份文件是筹备退休的 ” 现役 ” 文件,另一份是子过程正在重写的 ” 预备役 ” 文件。
大抵波及的内容就是如此了,接下来依照一个 AOF Rewrite 执行的工夫程序来看看到底产生了什么事
万般皆由长风起
先来看看,Redis AOF Rewrite 机制是怎么触发的呢?
有两种形式大家应该都很分明了,一种是配置文件中配置的若干工夫内,产生了若干键值对的变动,达到阈值就须要触发一次重写。
(这里补充一句,这个查看是在 Redis 后盾主线程中调度时查看的,这个工夫并不会是很确定的)
所谓的 serverCron 就是这个办法,何时触发,如何触发,咱们回头细说
另一种是客户端,要求执行 bgrewriteaof。
这里写的第三种,是在开启 AOF 时,才会进行一次。个别是在启动时就实现了 AOF 的启动。
但这里有一种非凡状况,就是在 Redis 有主从时。从服务在跟主服务同步数据时,主服务会生成一个 RDB 文件给从,从应用假客户端读取数据复原至内存。这个阶段,从服务是须要进行 AOF(如果原来开启的话)的。等到实现了主从同步的数据恢复后,主动开启 AOF,这里就会执行一次 AOF 的重写。
山一程,水一程,fork 子过程
不论是什么起因,当确定要进行 AOF Rewrite 之后,首先做的就是进行一系列查看,而后 fork 一个子过程。
有红线的中央就是 fork 子过程的中央。
if 语句中的是子过程须要执行的代码,else 中是主过程的。
先不焦急钻研奴才过程别离做了什么事,看下后面的校验。
首先,如果有 AOF 重写子过程或 RDB 重写过程存在的话,就不进行本次的重写。
其次,如果创立奴才过程的管道失败的话,也不进行重写。
创立子过程胜利之后,子过程就会立刻开始 AOF 文件的重写,而主过程则是持续提供对外服务,只是为了确保重写期间 AOF 不失落,会多做几步操作。
这里创立的管道非常重要,一共有三个:
提醒:因为 fork 子过程会让奴才过程共享内存,那么子过程肯定是要晓得主过程原有的数据存储在哪里的。
这里就波及到将原有主过程的页表复制一份过去的操作。这个操作是阻塞的,会导致 fork 操作卡住。
因而在流量大的时候,要留神 AOF Rewrite 将 Redis 卡住,而使 RT 增大。
话分中间
子过程
咱们看看子过程都做了啥:
- 创立一个临时文件,名字是 temp-rewriteaof-{pid}的文件,而后初始化一下文件句柄之类的援用。
-
判断是否是 RDB 混合模式,还是纯 AOF 模式,进行重写。这两者的区别,这里就不赘述了。
真正的重写其实很简略,就是挨个的读取 db 的内容,而后以对应的格局,写入文件中。
因为奴才过程之间有 ” 读时共享,写时复制 ” 的限度,也就是如果是读取时两者专用一份内容,当有人要写的数据时,会将原有数据 copy 进去一份,在新的 copy 的数据上批改,旧放弃不变。
Redis 就利用了这一性能,保障了读到的数据是 fork 之前的最初版本的数据。
到这里为止,其实是 AOF Rewrite 的外围逻辑,其余的逻辑都是围绕在 AOF Rewrite 期间有数据发生变化来做的。
整个重写是十分消耗 CPU 的,趁着子过程加班加点干活时,咱们来看一眼主过程在做什么。主过程
主过程在 fork 完子过程,把 ORK 交代给子过程之后,就对子过程不论不问了。偶然查看一下子过程有没有把工作实现(通信管道有没有新数据 / 子过程有没有隐没)。
假如在 AOF 重写过程中,有客户端发来了一个 set a 1 的申请,会将原来的 a 的值由 0 改为 1。
因为有奴才过程 ” 读时共享,写时复制 ” 的存在,不必放心子过程,它会读到老的数据。
在实现内存数据变动后,会走到上面这个办法中。咱们认真来看看。这个办法在 aof.c 文件中,所有写 aof 的操作都走这个办法。
它做了一下几件事: - Redis 是有多个 db 的,如果命令操作的 db 不是以后的 db,那么就会插入一条 select db 的命令。依据 ditcid 参数来确定
- 将带有过期工夫的操作,转换为 PEXPIREAT(EXPIRE/PEXPIRE/EXPIREAT/SETEX/PSETEX/SET EX seconds)
- 将操作的命令,转换为 RESP 格局
-
写入 AOF 相干缓存
这里与 AOF Rewrite 相干的,是步骤 4,咱们重点来看下这个步骤做了什么:首先,判断是否开启了 AOF,那显然咱们是开启了的啊,就须要将这条语句,写入旧的 AOF 文件中。这个很正当啊,万一重写失败了呢,数据不能够丢啊。
其次,如果有 aof 子过程 pid 存在,那么还要多做一步 aofRewriteBufferAppend(),这个是做什么的呢?
它是将刚刚生成的语句,再次保留在一个 aof_rewrite_buf_blocks 的构造当中。
这个 aof_rewrite_buf_blocks 是一个 list 构造,它保留的都是 10M 大小一个的 block。block 中存储的就是刚刚生成的语句。
而后办法返回。本次 aof 写入操作完结。
aof_rewrite_buf_blocks 的数据,会期待创立的管道 1 是否容许写入(写入机会由别的机制保障,这里略过),如果容许写入,就将数据写入这个管道中,而后将内存中写入局部的数据开释。提醒:这里须要留神,aof_buf 与 aof_rewrite_buf_blocks 是两个数据结构,外面的数据也是两份,不是专用一份。
因而重写阶段,数据变更会让主过程将这些数据在内存中存储两份,这对主过程是额定的压力。
子过程拿到第一个 KR 了
咱们把眼光再次回到子过程身上。
此时它曾经实现对原有数据的重写,拿到第一个 KR,咱们祝贺一下它~
当初咱们晓得了 Rewrite 期间变动的数据,是会通过管道告诉的。那么子过程是如何解决的么?
点点滴滴,聚水成河
其实在“子过程”章节中重写旧数据时,就开始解决了。子过程在重写旧数据时,会时不时的读取一下管道。rdbSaveRio 办法中
rewriteAppendOnlyFileRio 办法中
这些读取进去的数据都会保留在 aof_child_diff 的数据结构中。
这样旧数据会间接写入到 aof 重写文件中,期间变动的数据会保留在内存 aof_child_diff 中,数据程序就不会凌乱了。最初把这部分变动的数据对立写入待 aof 重写文件中即可。
最重要的是向上治理
子过程在实现第一个 KR,重写了旧数据之后,就快马加鞭的发展了下一项工作。从管道 1 中读取变动的数据。
这里有两个点须要留神,一个是主过程可能始终在承受新的数据,这就导致通道 1 中永远有数据,子过程就无限度的读取数据,这必定是不能承受到,因而,限度了最多只会读取 1s。
另一个,如果始终没有数据,也没必要始终等啊,大家工夫都很贵重的,如果 20 毫秒没有数据,那么子过程也就不会读取了。
上面就到了要害一步,要告诉主过程本人的工作曾经实现了 80%,进行向上治理了。
子过程向通道 2 写入一个!号,告诉主过程,请进行向管道 1 当中写入数据
主过程承受会邀
主过程在承受到通道 2 的告诉之后,将 aof_stop_sending_diff 设置为 1,变动的数据仍旧进入 aof_rewrite_buf_blocks 中,然而不会在写入通道 1 中了,全副停留在本人的内存中。
主过程接着向通道 3 中写入一个!,表明本人进行写入数据。
临门一脚
子过程收到告诉后,最初将通道 1 中的数据全副读取进去,而后刷入磁盘,并将 aof 重写文件重写命名为 temp-rewriteaof-bg-{pid}.aof,而后本人退出过程。
这里的写入,就是指将内存中的 aof_child_buf 中的数据一股脑的写入文件当中,而后随着过程退出,内存一并开释。
主过程:我来兜底!
此时子过程曾经实现了它的工作,退出了过程,主过程在 serverCron 中发现子过程曾经不存在了之后,就调用 backgroundRewriteDoneHandler 办法,解决善后的工作。
将之前 aof_rewrite_buf_blocks 还存在的数据,写入 aof 重写文件之中。(temp-rewriteaof-bg-{pid}.aof)
将 aof 文件设置为重写文件,重写 aof 正式转正,旧文件退休。
道由白云尽,春与青溪长
到此为止,一次残缺的 AOF Rewrite 重写就完结了。
纵观整个过程,咱们能够看到,外围的重点是如何解决 AOF Rewrite 期间变动的数据。
为了保障这部分的数据正确,Redis 5.0 版本总共应用了两个内存构造存储 (主过程的 aof_rewrite_buf_blocks,子过程的 aof_child_diff),两个磁盘 IO(主过程写入旧 AOF 文件,子过程写入新 AOF 重写文件),三个通信管道,双倍的 CPU 开销来实现。
有趣味的同学能够理解下还未正式公布的 Redis 7.0,Multi Part AOF 的实现。这个版本的实现完满解决了上述的冗余问题。
好的,明天的分享就到此为止啦,感激大家。