自学内容网 自学内容网

【Redis】AOF持久化的实现与原理

一、为什么需要AOF持久化?

我们知道Redis是基于内存存储的数据库,那么万一Redis发生故障或者服务器宕机了,内存中的数据就会消失。为了能更安全的保存数据,也为了Redis恢复时能够找回之前的数据,我们需要把内存中的数据持久化到磁盘里。

为此,Redis先是提供了RDB持久化,把所有数据库中的数据保存到RDB文件当中。但是RDB有一个问题,就是 每次都保存所有的数据,消耗是很高的,系统不可能每隔一会就去做一次RDB持久化吧。但如果每次RDB的间隔很久的话,万一这中间Redis挂掉了,岂不是会丢很多数据?所以开发者自然而然会想到,能不能让每次保存的数据少一点,持久化的粒度缩小一点,这样不就能更大程度上保证数据的完整性了吗?因此,AOF持久化应运而生。

二、AOF是怎么实现的?

2.1 开发的自我修养

在学习真正的实现之前,大家不妨先想象一下,如果你是Redis 的开发者,产品经理给你提了一个需求,要把Redis 执行的每一条写命令保存下来,你准备如何实现?

想一下,每条命令(只包括写命令哈,读命令不改变数据没必要保存,下面都简称“命令”,大家明白就好)都保存到磁盘的话,直接把每条命令写入磁盘肯定不行,这IO太高了。那就先把命令保存到内存中的一个缓冲区,再定时把缓冲区的数据刷到磁盘上。 那问题又来了,每隔多久做一次刷盘呢?嗯,这似乎应该做成一个配置项,让用户自己去选择。
想想还有什么其他问题呢? 客户端的命令是持续不断地发送上来的,那我们做AOF持久化的过程中,得把缓冲区锁死吧,不然这命令不是没完没了地保存不完了?如果缓冲区锁死,那新执行的命令要保存在哪呢?…

带着这些问题,我们来看一下真正的实现。

2.2 AOF 持久化的实现

AOF 持久化功能的实现可以分为三个步骤:命令追加、文件写入和文件同步。

命令追加

当AOF 功能开启时,Redis 服务器每执行完一个写命令,都会将这条写命令追加到服务器的aof_buf缓冲区的末尾。
当然,Redis并不是把命令原样写进去的,而是以一种协议格式写入,不是很好看,但也能辨认出命令是啥。

文件的写入与同步

在把命令写入缓冲区之后,Redis 会判断现在是否要将aof_buf 中的内容写入和保存到磁盘中的AOF 文件。这时候就要看配置选项了,在 redis.conf 配置文件中的 appendfsync 配置项可以填以下 3 种参数:

  • always – 每次执行写命令都将aof_buf 中的数据写入并同步到AOF 文件。
  • everysec – 每隔一秒,将aof_buf 中的数据写入并同步到AOF 文件。
  • no – 将aof_buf 中的数据写入到AOF 文件,但不同步,何时同步由操作系统决定。

这里先明确一下文件写入文件同步是什么意思:

在现代操作系统中,为了提高写文件的效率,在用户调用write 函数之后,操作系统不会立刻把数据写入磁盘,而是先把数据保存到内存中的一个缓冲区,等缓冲区满了或者到了一定的时间,再真正地把缓冲区中的数据写入到磁盘中。
当然,OS也提供了fsyncfdatasync这两个函数,可以强制将缓冲区中的数据立刻同步到磁盘

所以,AOF文件从命令被追加到命令真正被写入磁盘其实经过了两道缓存,大致流程如下图所示:
在这里插入图片描述
从上面三种持久化参数我们不难看出,always肯定是最安全的,就算出现故障宕机,也最多丢一个命令,但同时它的效率也是最慢的,毕竟每次执行命令都要写文件;
everysec 的效率足够快,而且出故障最多丢1秒的命令数据,企业大多使用该策略,那万一真的宕机了,丢失的这1秒内的数据怎么办?对于企业来讲,Redis基本上是集群部署的,用多个节点保证数据的完整;
no这个策略下,数据何时被写入磁盘由操作系统决定,太不可控,所以用这个的比较少。

三、AOF重写

看完AOF 持久化的实现,我们不难想到另一个问题,那就是AOF 保存的太细致了,随着时间的推移,保存这么多命令必然会占用大量的空间。 而且有很多这种场景:一个同学不停地插入删除某个KEY,插入之后立马再删掉,就这么来回重复,到最后AOF 文件里面保存的岂不都是无用的命令?这显然不太合理,因此,Redis为AOF 提供了重写机制。

AOF 重写的实现

Redis 将创建一个新的AOF文件并覆盖原有AOF文件,以此完成AOF文件的重写。而且整个重写过程并不会读取或修改老AOF文件,而是直接从数据库中读取现有的数据,再对新AOF文件进行写入。

也就是说,Redis 会遍历数据库中所有的键,然后用命令去记录一个键值对。这意味着,即便以前有很多命令对某个Key做了修改,在重写时也只需要一条命令去记录,这就达到了压缩文件的目的。

Redis 会起一个子线程去做AOF重写,所以不会阻塞主线程,Redis还是能正常执行命令。但这又产生了另外一个问题,就是子线程在重写AOF时,主线程执行的命令更改了数据库,那子线程保存的AOF文件中的数据不就跟数据库中不一致了吗?

为了解决这个问题,Redis 设置了一个AOF重写缓冲区,当服务器在做AOF重写时,新执行的命令不仅会被追加到AOF缓冲区,还会被追加到AOF重写缓冲区。当子线程执行完AOF重写之后,会发一个信号给主线程,主线程会把AOF重写缓冲区里的数据写入到新AOF文件中,然后给新AOF文件改名,原地替换老的AOF文件。

这么做可以保证两点:

  1. AOF缓冲区的内容还是正常保存到AOF文件中,对老AOF文件的处理工作正常进行;
  2. 从创建子进程开始,执行的写命令会被同时记录到AOF重写缓冲区,并在重写工作完成后保存到新AOF文件中。最终,新AOF文件中的数据与数据库中是一致的。

四、混合持久化模式

一般来说,没有哪个企业会只开RDB或者只开AOF,都是两个一起用的。

在混合模式下,RDB文件和AOF文件合二为一,前面一部分是RDB格式,后面一部分是AOF格式。当重新启动下一个Redis实例时,会先加载前面的RDB部分,再追加执行后面的AOF部分,这样既保证了空间利用率,又可以保证命令保存的时效性和完整性。

总结

AOF持久化这一块就到此为止了,我认为从功能的源头出发,理解开发者为什么要做这个功能,会更容易记忆和理解其实现。


原文地址:https://blog.csdn.net/weixin_43390123/article/details/145244004

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!