Redis设计与实现(八)

版权声明:@Wrial https://blog.csdn.net/qq_42605968/article/details/88937608

Redis持久化

AOF持久化

什么是AOF持久化?

在前边也说了RDB持久化,AOF(Append only File)也是保存数据库状态的一种方式。它和RDB持久化的区别就是RDB是存的数据,而AOF存的是命令。

AOF的实现

AOF的实现可以分为命令追加,文件写入,文件同步三个步骤。

  1. 命令追加

在AOF功能打开时,服务器每执行一个指令后,它会以协议格式将被执行的命令追加到服务器状态的aof_buf缓冲区的末尾。这也就是命令的追加。

  1. 文件写入与同步

前文也提到过,Redis服务器的进程就是一个事件循环,在循环的过程中负责接受和回复客户端的请求。在执行客户端命令时,有可能会处理一些文件,可以也会操作一些写命令,使这些命令追加到aof_buf后,因此在服务器每处理完成一个事件前,会调用flushAppendOnly()函数,函数里也包含由是否将buf缓冲区的文件写到AOF文件中。而此函数由服务器配置选项的appendfsync来决定。默认为everysec

| appendfsync | flushAppendOnlyFile |

| allways | 将缓冲区的所有写入并同步到AOF文件 |

| everysec | 将缓冲去所有写入AOF文件,如果距离上次同步超过1s,那就再次进行同步(这个同步操作是一个线程专门负责执行的)|

| no | 将缓冲区文件写入到AOF,但是不进行同步(何时同步由操作系统决定)|

对比上边机中方式各由什么优缺点,第一种和第二中中有同步函数,可以强制让操作系统立刻将缓冲区的数据写入硬盘,保证了数据的安全性。第三种方法,提高了文件写入的效率,调用write函数将数据写入到文件时,会暂时将数据保存在缓冲区,当缓冲区被填满或者超时时才会将数据写入硬盘,这样的问题是不安全,并且极其容易造成数据丢失。

AOF文件的载入和数据还原

前文说到过,AOF存的是命令也就是也就是说它下一次恢复的时候就再把这些命令重新执行一边就又回到上一次同步时的状态了。

AOF文件读入并还原:

  1. 刚开始会创建一个不带网络的伪客户端(带不带网络数据恢复的结果是一样的,并且Redis命令只能在客户端执行,所以创建一个伪客户端)。

  2. 服务端从AOF文件读一条命令。

  3. 伪客户端执行传过来的写命令。并不断重复上一步和这一步,直至命令全被处理完。

AOF重写

什么是文件重写?

随着服务器的不断运行,AOF文件也是越来越大,不加以控制的话则有可能会对Redis服务器和计算机造成影响,并且文件越大,还原时需要的时间也就越多,那有没有什么方法来解决这些问题呢?因此就出现了文件重写(rewrite),使用该功能Redis服务器就会新建一个AOF文件,对原来的AOF文件进行精简(怎么个精简法看后文解释)并且让恢复后的各个数据库状态和普通AOF文件写入后载入相同。

解密怎么进行文件重写

文件重写并不是对原来的文件重新进行读取或者删改操作,是通过读取服务器当前的数据库状态来实现的。

比如举一个例子,我们创建一个集合对象,并每次向集合添加一个元素,添加了很多元素后,AOF文件就会越来越大,我们需要恢复这些数据的时候,如果按原始的AOF文件来的话还是要一条一条的写并且冗余程度太高。因为,这些条指令可以用一条指令来代替,就是一次性将这些数据添加到集合里,所以,文件重写就是利用这样的方式。

文件重写的步骤

遍历所有非空数据库,并写入每个非空数据库的编号,遍历数据库中所有的键(忽略过期键)根据各个类型进行重写(尽量减少命令的冗余),如果键带有过期时间,那过期时间也要被写入。因此这样重写AOF文件,就不会造成硬盘空间的浪费。

但是这种情况不是绝对的,在重写程序处理列表,哈希表,集合,有序集合这四种可能会又很多元素的键时,会先对此键所包含的元素数量进行检测,如果超过了REDIS_AOF_ITMES_PER_CMD的值就会用SADD命令记录这个集合。以当前版本64为例,如果一个集合有200条数据,它就会先SADD64个数据,然后再使用SADD64个数据,然后再使用SADD64个数据,然后再SADD剩余的数据。(其余机中类型也如此)

AOF的后台重写

虽然AOF重写可以很好的完成一个AOF文件,但是在很多时候数据量很大如果在主进程进行重写的话,会阻塞进程而使其他工作在此期间不能进行。因此,这样做肯定不是一个好的注意。所有,还是采取了和前边生成RDB文件后台执行相同的思想,派生出一个子进程专门来完成这件事情

后台重写的好处

  • 在重写期间服务器(父进程)可以处理客户端来的命令。
  • 在子进程中带有服务器数据的副本,使用子进程(不是子线程)可以在避免用锁的情况下完成任务,保证了数据的安全。

使用子进程而不是线程带来的问题?

使用子进程而不是子线程会导致文件状态可能不一致,因为在AOF重写期间,父进程要处理一些命令,新的命令可能会使数据库状态发生改变,然而子进程并不知道这些东西,这个问题该如何解决呢?

为了解决这一问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在服务器创建子进程后开始使用,每当Redis服务器执行完一个写命令后,会同时将这个命令发送给AOF缓冲区和AOF重写缓冲区。

因此在子进程执行AOF重写期间,服务器进程需要执行以下三个工作:

  1. 执行客户端法来的命令。
  2. 将执行后的命令追加到AOF缓冲区。
  3. 将执行后的命令追加到AOF重写缓冲区。

这样的好处是什么?

  • 首先AOF缓冲区的内容定期被写入和同步到AOF文件中,对现有的AOF文件处理工作会正常进行。(注意AOF是始终存在的只不过重写的AOF更加精简)
  • 从创建子进程开始,所有的写命令都会记录在AOF重写缓冲区里。

和前边RDB一样,子进程完成后会发一个信号给父进程,然后父进程接受到信号会调用一个信号处理的函数并执行以下的工作:

  1. 将AOF重写缓冲区的所有内容写到新的AOF文件中,这时的AOF文件保存的数据库状态和服务器当前状态是完全一致的。
  2. 对新的AOF文件进行改名,并原子性的替换掉现有的AOF文件。

这样就彻底解决主进程阻塞的问题了吗?当然不是,为了保持同步,父进程在调用信号处理函数的时候会阻塞。虽然主进程会有短暂的阻塞,但是这已经是既能减少阻塞时间,又能保证AOF同步的最好方法了!

猜你喜欢

转载自blog.csdn.net/qq_42605968/article/details/88937608