从前有句古话说得好,天将降大任于斯人也,必要先看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的实现。这个版本的实现完满解决了上述的冗余问题。
好的,明天的分享就到此为止啦,感激大家。
发表回复