深入浅出AOF功能和AOF重写两个知识点

本文默认你是使用过redis并了解redis的基础概念,学习redis入门并不难,给你介绍各种API使用也没啥意思。在这里 不会给你堆各种专业词汇,只有我个人理解的大白话

这里没别的 先来张总原理图,花10秒时间记一下,跟游戏打副本一样,先看下地图 做到脑子里有个大概的脉络,这里也是平时工作中比较常用的一种工作思考方式,遇到难的问题如果没思路就先忙别的,我们的大脑会在后台保持这个问题并且会时不时的激活一下,同样的时间做了多件事。高考时老师说拿到试卷先看一遍最后的作文题目在开始答题,一样的思想

AOF功能工作原理和AOF重写原理总图:


10秒已过 ,开始正文
一。AOF
    1.说白了就是个日志记录文件。redis会把所有写命令记录到一个设定好的日志文件中,做开发的都知道 存储日志 是需要有             特定的格式的,这样后面你才能快速的定位检索,AOF里存储的是Redis自己定义的RESP协议格式的字符串,对,你没                看错就是字符串,(resp协议的https://blog.csdn.net/wenmeishuai/article/details/106101762
     2.AOF里记录的是每一次写命令,比如说list数据类型,其中有100条数据,在AOF中可能就有100条RESP协议的记录,                   参考RESP的文章就知道,这其中有大量的重复命令

    3.开启AOF持久化功能只需改两个配置参数
        appendonly yes 开启aof
        appendfilename mads.aof 日志文件名字,随便起
    4.人是个喜欢刨根问底的生物,用是会用了。它是怎么实现的?
        1)上面图中 红色框内是AOF的流程。 主进程接收客户端请求写命令,写入到aof_buf(aof缓冲区)然后主进程就返回了                    (redis的优化点)
        2)有专门的子进程去调用fsync()函数把数据从aof_buf写入到aof文件(谁在说redis是单线程就对他说:哼 初级程序员)
        3)结合上面2)子进程该什么时候去触发调用fsync()这个同步动作呢,redis已经帮我们提前设置了三种策略:
            ps:fsync()函数要求操作系统马上把aof_buf数据写到磁盘上.
            配置文件中
                appendfsync always 
                appendfsync everysec 
                appendfsync no  
                # no:不要立刻刷,只有在操作系统需要刷的时候再刷 ,比较快。如果redis重启了拿到的数据将不是很新的数据 
                # always:每次写操作都立刻写入到aof文件。慢,但是最安全。 
                # everysec:每秒写一次。折衷方案。 (默认,如果拿不准就用 "everysec"试水 )
         4)AOF追加阻塞
            结合上面继续深入,如果上面我设置的是每1秒同步一次数据,在线上大批量写请求下aof_buf有大量数据需要同步,此时              就会对磁盘进行高频写入,磁盘IO就变成了瓶颈,就会出现上次的同步动作还没完成
            主进程又接收到大批写命令写到了缓冲区,此时redis为了保证aof文件安全性,会阻塞主线程,直到上次fsync同步完成。
            主进程吧数据写入到aof_buf后会对比上次fsync操作时间,此时有两种情况:
                1.如果距离上次fsync操作时间大于2S则阻塞主进程直到fsync结束
                2.如果距上次操作时间小于2S则主进程直接返回
            这里虽然我们配置的是每秒同步一次,但是实际上不是丢失1S的数据,实际上可能丢失2S数据,这里请细品
         5)aof_buf同步到文件的流程  执行之前,看总量,依次执行,执行完成了再回收(清空)缓冲区

                  
         6)aof_buf里存储的格式是  RESP协议格式,(还没有看源码确认)
   
二。AOF重写
     1.通过上面已经知道了AOF记录的是字符串。真正线上环境写操作是很多的,AOF的文件大小增加也是很快的,如果一个                AOF文件有10G了再去追加的时候那是非常慢的了。
     2. redis就提供了一种压缩的方式,比如还是上面list数据类型100条数据,经过压缩以后就变成了一条,这就是AOF重写。
     3.AOF重写会触发Redis的缓存淘汰策略,可以参考aof_rewrite函数源码,参考这篇文章                                                     https://blog.csdn.net/qq_41453285/article/details/103301715

具体流程:

    1.Redis开启了持久化功能,并且达到了重写的条件。三个参数,

        auto-aof-rewrite-percentage 100 
        auto-aof-rewrite-min-size 64mb
        # 自动重写AOF文件 
        # 如果AOF日志文件大到指定百分比,Redis能够通过 BGREWRITEAOF 自动重写AOF日志文件。 
        # 工作原理:Redis记住上次重写时AOF日志的大小(或者重启后没有写操作的话,那就直接用此时的AOF文件), 
        # 基准尺寸和当前尺寸做比较。如果当前尺寸超过指定比例,就会触发重写操作。 
       # 你还需要指定被重写日志的最小尺寸,这样避免了达到约定百分比但尺寸仍然很小的情况还要重写。 
       # 指定百分比为0会禁用AOF自动重写特性。

    2.调用fork系统级别函数,复制出完全一致的一个子进程,和主进程共用同一块内存空间(类似浅复制,redis为了节省内存开         销的优化点)

    3.子进程调用aof_rewrite函数(redis客户端执行bgrewriteaof命令最终也是调用此函数)可以创建新的AOF文件去执行重写操作           根据已有数据进行命令的压缩和过期时间的检测并将压缩后的命令写入到新的AOF文件,直到写完

    4.在AOF重写过程中,主进程是可以继续对外服务的,当接收到写命令,写入到aof_buf后,然后判断此时是否正在执行重写           操作,如果是再将写命令写入 到AOF重写缓冲区,主进程返回

    5.当子进程完成对AOF文件重写之后,它会向父进程发送一个完成信号,父进程接到该完成信号之后,会调用一个信号处理函         数,该函数完成以下工作:
         1).将AOF重写缓存中的内容全部写入到新的AOF文件中;这个时候新的AOF文件所保存的数据库状态和服务器当前的数                   据库状态一致;
         2).对新的AOF文件进行改名,原子的覆盖原有的AOF文件;完成新旧两个AOF文件的替换。到这里才是一次完整的AOF                   重写流程
         3).当这个信号处理函数执行完毕之后,主进程就可以继续像往常一样接收命令请求了。在整个AOF后台重写过程中,只有                 最后的“主进程写入命令到AOF缓存”和“对新的AOF文件进行改名,覆盖原有的 AOF文件。”这两个步骤(信号处理函数                 执行期间)会造成主进程阻塞,在其他时候,AOF后台重写都不会对主进程造成阻塞,这将AOF重写对性能造成的影                   响降到最低。

      6.如果AOF重写失败 redis会删除中间临时产物,保证流程的健壮性

AOF后台重写为什么这么干
1.aof_rewrite这个函数会进行大量的写入 操作,所以调用这个函数的线程将被长时间的阻塞,因为Redis服务器使用单线程来处理命令请求;所以如果直接是服务器进 程调用AOF_REWRITE函数的话,那么重写AOF期间,服务器将无法处理客户端发送来的命令请求;
2.Redis不希望AOF重写会造成服务器无法处理请求,所以Redis决定将AOF重写程序放到子进程(后台)里执行。这样处理的最     大好处是: 
     1)子进程进行AOF重写期间,主进程可以继续处理命令请求;
     2)子进程带有主进程的数据副本,使用子进程而不是线程,可以避免在锁的情况下,保证数据的安全性。
          使用子进程进行AOF重写的问题
3.子进程在进行AOF重写期间,服务器进程还要继续处理命令请求,而新的命令可能对现有的数据进行修改,这会让当前数            据库的数据和重写后的AOF文件中的数据不一致。
   如何修正
     1.为了解决这种数据不一致的问题,Redis增加了一个AOF重写缓存,这个缓存在fork出子进程之后开始启用,Redis服务器主          进程在执行完写命令之后,会同时将这个写命令追加到AOF缓冲区和AOF重写缓冲区
    2.即子进程在执行AOF重写时,主进程需要执行以下三个工作: 
       执行client发来的命令请求;
       将写命令追加到现有的AOF文件中;
       将写命令追加到AOF重写缓存中。

最后记录下我的思考 
            1.写文件是很耗时的,数据多时还会出现阻塞。熟悉操作系统的同学会有个印象,数据从缓冲区到磁盘的过程也要经过内                  核态到用户态的转换出现上下文切换,这个过程会很长,(Netty零拷贝可以看看)
            2.Redis设计初衷就是快,10W+的QPS 也就是1ms就要处理100个命令,阻塞对于redis无疑是不可接受的,如果你是                       redis作者会怎么解决呢,很简单的能想到就是同步转异步嘛,让会产生阻塞或者耗时的操作由子线程去干就好了,                      主进程就专注处理客户端请求就好了(重点记忆,主进程只跟客户端打交道,其他全交给子进程),专业的人干专业                    的事,人类发展史中 分工产生效能 也是这个思想。
            3.追加阻塞这里,为什么不采用队列或多线程的方式来让主进程迅速返回?考虑是 增加额外的处理肯定要增加技术复杂                   度和资源的消耗,。。。
            4.其实最后发现并没有什么高大上的解决方案对不对。基础真的很重要呀。并且优化是从各个细节来的,也是个持续的                     过程,成熟的架构都是慢慢演进的,

最后抛几个问题看看掌握没有

1.redis是单线程么?

2.为什么要子进程呢?因为万一重写失败。不会造成redis的死掉

3.为什么使用子进程而不是线程?可以避免在锁的情况下,保证数据的安全性。

4.为什么主子公用内存空间? 省空间,redis是内存数据库,留着内存干正事不香么

redis配置参数很全的一篇

https://www.cnblogs.com/xuliangxing/p/7151685.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章