【Redis笔记】一起学习Redis | 聊聊Redis的持久化策略,AOF和RDB

Redis · 2019-08-02

聊聊Redis的持久化策略,AOF和RDB


如果觉得对你有帮助,能否点个赞或关个注,以示鼓励笔者呢?!博客目录 | 先点这里

  • 前提概念

    • 持久化的作用
    • Redis持久化的方式
  • RDB持久化策略

    • 什么是RDB?
    • RDB的两种策略方式
    • 如何使用RDB策略备份数据?
    • RDB的原理是什么?
  • AOF持久化策略

    • 什么是AOF
    • AOF的原理是什么
    • AOF的三种策略
    • AOF的重写机制
  • RDB和AOF的抉择

    • RDB还是AOF?
    • Redis4.0的混合持久化
    • 运维最佳实践
  • 其他问题

    • RDB持久化操作时,子进程拷贝父进程的数据副本用于持久化,不会增加内存消耗吗?
    • 为什么Redis的AOF机制是先成功命令才再记录日志?

前提概念


持久化的作用

什么是持久化?

  • 我们将能把内存中的数据保存到磁盘中存储的行为就称之为持久化

为什么需要持久化?

  • Redis所有的数据保存在内存中,如何Redis实例突然的宕机,其占用的全部内存就会被系统释放,导致Redis的数据全部丢失,因此必须要有一种机制来保证Redis的数据不会因为故障而丢失。这一机制就是Redis的持久化机制。

所以持久化操作的作用就是把Redis中的数据,保存到磁盘中。避免因为实例宕机,而出现数据丢失的现象


Redis持久化的方式

Redis为我们提供了主要的两种持久化方式

  • 数据快照 (RDB)
    这种行为就类似MySQL的Dump ,redis的RDB,这就是某时某个点的全部数据备份
  • 日志文件(AOF)
    每次的操作就写入日志,当我们需要恢复数据时,就可以根据日志的记录完整的走一遍流程,恢复数据,比如MySQL的BinLog,Hbase的HLog,Redis的AOF

RDB持久化策略


什么是RDB?

什么是RDB数据快照?

  • RDB文件是一个二进制文件,存储在硬盘当中。其实就是之前用过的MySQL dump,我们把某个时刻的数据导出成RDB文件,需要的时候,导回去恢复数据即可

RDB的两种策略方式

  • save(同步)
    我在redis client执行save命令,它就会在Redis安装目录下生成一个RDB文件,在数据量比较大的时候,会造成一个同步阻塞。如果存在老的RDB文件,则替换。时间复杂度是O(n)

  • bgsave命令(异步)
    使用了linux的fork()函数生成了主进程的子进程,让这个子进程去完成RDB的生成。生成完毕后,会通知主进程,我们的RDB文件生成成功了。时间复杂度也是O(n),只不过不阻塞主进程


如何使用RDB策略备份数据?

支持自动,手动两种方式进行RDB持久化
redis可以通过客户端主动持久化,输入命令,生成RDB文件,也可以通过配置来等满足条件时自动持久化,生成RDB文件

通过客户端输入config get dir就可以知道Redis的数据存放路径。
通过客户端输入config get dbfireaname就可以知道rdb文件的名称

  • 手动持久化
    手动在客户端输入save 或 bgsave命令
  • 自动持久化
    通过配置,使得Redis在某个条件的时候自动触发RDB文件的生成,比如save m n配置
    save 900 1 900秒内至少有1个key被改变则做一次快照
    save 300 10 300秒内至少有300个key被改变则做一次快照
    save 60 10000 60秒内至少有10000个key被改变则做一次快照

什么情况下会主动触发Redis的RDB快照机制呢?

  • 主从复制时,从库全量复制同步主库数据,此时主库会执行bgsave命令进行快照;
  • 客户端执行数据库清空命令FLUSHALL时候,会触发快照的生成
  • 客户端执行shutdown关闭redis时,会触发快照的生成

RDB的原理是什么?

(一) RDB文件如何生成?

  • RDB快照文件即使就类似MySQL的DUMP文件,它就是一个快速数据,对某个时间节点的数据来个快照,然后备份到磁盘中。
  • 说白了当你要进行快照持久化时,Redis就会遍历内存中的所有数据,然后将数据序列化后写入到磁盘中的RDB文件

(二) 单线程模型的Redis进行RDB持久化,难道不会阻塞其他业务请求吗?
我们知道RDB持久化策略就是Redis在某个时间节点,扫描内存中的所有数据,生成一个二进制RDB文件,保存到本地。但是我们知道Redis是单线程模型的进程,既单个线程在处理客户端请求的同时,还要花点时间处理持久化生成RDB文件的耗时操作。那么问题就来了,生成RDB文件如此耗时,那么持久化的过程不就会阻塞我们正常的业务请求了吗?

答案是肯定会的,所以Redis为了解决这个问题,采用的是操作系统中的多进程COW机制 (Copy On Write) 来实现快照持久化

(三) 多进程COW机制 (Copy On Write)原理

什么是Copy On Write?

  • 相信Copy On Write这个名词,应该很多开发人员都知道,其作用就像Java中的CopyOnWriteArrayList的数据结构一样。当有人要写一个cow对象时,我们不会直接在该对象进行操作,而是复制一份它的副本数据,对该副本进行写操作,最后将修改后的副本替换原数据。它的作用就是可以实现对cow对象做到并发读-写不冲突,读-读不冲突。唯有写写才冲突

那什么又是多进程Copy On Write呢?

  • 我们都知道Redis是单线程模型,意思就是单个进程单个线程。但是实际上,Redis并非真的是单进程/线程的去处理事情。既主要的工作的确是单进程/线程模型,但也有一些很重要的,但比较耗时的操作,Redis并不是交给主进程/线程去做的,而是扔个子进程/线程去做异步操作!!
  • 既Redis在持久化时会调用glibc的函数,从主进程中fork出一个子进程,RDB快照持久化操作就是完全交给子进程异步去实现,而父(主)进程依然正常的继续处理客户端的业务请求。这里描述的就是多进程的体现
  • 而Copy On Write的体现就在于,RDB持久化操作,生成的是某一个时间节点的数据。比如我在12:00开始生成快照,那么不管之后Redis是否又对内存的数据做了什么操作,但我的RDB数据快照就一定是12:00这个时刻的数据。所以如果在12:00之后,还有其他客户端的写操作请求,那么Redis的父进程就会拷贝一份12:00的完整副本数据,专门用于做数据修改,而原数据则被子进程用于做RDB持久化的操作。所以主线程在12:00之后对数据的增删查改,是不会反应到快照数据中的。

AOF持久化策略


什么是AOF

什么是AOF?

  • AOF就是一个日志文件,对Redis的每一条命令都会以AOF格式写入AOF文件中。需要恢复时,就导入AOF文件去执行里面的记录就可以了,而且记录肯定是实时的,毕竟每执行一条命令就会有相应的纪录
  • AOF日志存储的是Redis服务器的顺序指令序列,AOF日志只记录对内存进行修改的指令
  • AOF日志的记录并不像MySQL, HBase等数据库先写入日志再执行操作,而是先执行成功命令,再将命令记录到日志文件中。
  • 通过在配置文件配置appendonly参数为yes, 以开启AOF持久化

AOF的三种策略

(一) AOF写入原理
我们知道了Redis的AOF文件实际就是记录Redis实例的所有涉及修复内存的指令,但是我们要记录一个记录日志的特性

  • 但是这些修改指令的记录并不是直接刷到磁盘文件中的,而是①首先将内容写入内核的内存缓冲区,②然后再根据策略从内核异步刷新到硬盘的AOF文件中。
  • 这是为什么呢?因为这是一个效率问题,如果直接写入硬盘,速度较慢,有瓶颈。所以先写入缓冲区,保证吞吐量,然后再异步的刷新到硬盘中

(二) AOF写入的三种策略
所以Redis给我们提供了三种AOF策略,以供用户按需选择

  • always
    实时刷新,将缓冲区的每一条命令都立即刷新到硬盘的AOF文件。既每执行一条命令,待其刚刷入缓冲区,就立即强制刷入磁盘AOF文件中

  • everysec
    每一秒执行一次,既每一秒都强制将缓冲区的内容刷新到硬盘AOF文件中。

  • no
    不强制刷入,no就是不需要我们来考虑什么时候刷入磁盘,而是由操作系统来决定,他想什么时候刷新就什么时候刷新

(三) 三种策略的优缺点比较

  • always实时刷新策略的好处是可以保证,AOF文件是实时更新的,可以保证AOF日志肯定是完整的,不会出现AOF日志内容丢失的情况。但是缺点也很大,就是每次写操作,就要立马刷入日志,使得Redis的内存操作者和磁盘IO操作被同步捆绑,以至于产生大量的同步IO操作,造成Redis的写性能严重下降。影响了Redis高性能的口碑
  • everysec每秒一次策略,可以保证AOF文件每秒都能刷入一批指令记录。既在安全性和性能上进行平衡考虑的折中策略。好处就是在保证性能的同时,一定程度的保证数据的持久性,最多丢失1s的数据。缺点就是在高并发的场景下,1s中也可能会产生大量缓存数据,丢失1s的重要数据可能会造成生产事故
  • no这个机制比较少用,因为基本上都不能用在生成环境中。因为同步日志的时间间隔不能确定,完全靠操作系统自主决定。所以一旦宕机,可能会丢失一段时间的数据。

AOF的原理是什么

(一) AOF日志文件生成的原理?
AOF日志存储的是Redis服务器的顺序指令序列,AOF日志只记录对内存进行修改的指令。 假如AOF日志记录了自Redis实例创建以来的所有修改性指令,那么就可以通过对一个空的Redis实例顺序执行AOF日志中的所有指令,以“重放”的行为来复制或恢复整个Redis的内存数据结构状态。

  • Redis在收到客户端的修改指令后,会进行参数校验,逻辑处理的操作,在没有发生错误的情况下,就会立即将该指令写入AOF日志文件中。这里不同于MySQL先写入biglog,再执行命令的顺序,Redis是先执行命令成功后,才写入日志的。
  • Redis在长期运行的过程中,AOF的日志会越来越长。如果实例宕机重启,重放整个AOF日志的操作会非常的耗时,可能会导致Redis长时间无法对外提供服务,所以需要定期对AOF日志进行相应的瘦身

(二) fsync是什么鬼!?

我们知道AOF日志是以文件的形式存储在磁盘中,但是当Redis对AOF进行日志写操作的时候,①实际是先将内容写到内核为该文件描述符分配的一个内存缓存中,②然后内核会异步的将数据刷到入磁盘文件

所以一旦机器突然宕机,如果AOF日志此时如果还有部分还在内核缓存中的内容没刷入文件的话,也就会产生AOF日志丢失的问题。我们知道也了解到了AOF机制又三种策略,每种策略导致的持久化效果有所不同,造成的数据丢失影响也不同。而造成他们的唯一区别就是 “内核什么时候将数据刷入磁盘文件” 。那么AOF三种策略是如何去影响内核什么时候刷入磁盘文件的呢?

fsync函数的作用

  • 这就是fsync函数的作用啦!!Linux的glibc提供了fsync(int fd)函数,其可以将指定文件的内容强制从内核缓存刷入磁盘。
  • always策略实际就是每执行一个命令,就执行一次fsync函数。而everysec策略则是定时每秒执行一次fsync含。no则是不主动干预操作系统,完全放任系统自身去调节这个问题,一般就是等内核的缓冲区满了之后,再刷入磁盘

AOF的重写机制

为AOF日志文件进行瘦身

  • 我们知道,随着时间的逐步推移和命令的写入,AOF文件会越来越大,此时AOF文件就会显得非常臃肿,每次重放AOF日志时,也需要耗时非常大。那怎么办呢?有什么好的办法解决这个问题呢?答案还是有的,那就是对AOF日志文件进行 “重写”
  • AOF日志重写的目的很明确,就是减少硬盘占用量加速日志重放速度

为什么重写可以瘦身呢?

  • 比如我们先将key=a的值修改1, 然后再将a的值修改为2,依次循环,每次修改a值+1,总共循环1000次,那么最终键a的值就为1000。其对应的意思就是说AOF日志文件中也有1000条修改指令。如果我们拿它进行重放操作,最终Redis的值也是1000。
  • 但是有没有什么办法,可以将日志文件中的1000条修改记录削减一下,也能重放时,让键a的值等于1000呢? 有啊,很简单啊,那就是直接写入一个命令让键a的值等于1000不就行了吗?这样AOF日志也只有一个记录了,从1000 -> 1,大削减压!!
  • 没错,重写AOF日志就是这样的一个过程,因为就AOF日志作用的本质来说,它更在意的是结果,而不是过程。既不管通过什么样的过程进行重发,只要最后的结果是正确的,那就没问题了。

重写AOF的原理 ? !

  • Redis提供了bgrewriteaof指令,用于对AOF日志进行瘦身,其原理就是开辟一个子进程对当前内存数据进行遍历,然后转换成一系列的Redis的操作指令,序列化到一个新的AOF日志文件中。序列化完毕后再将操作期间发生的增量AOF日志追加到这个新的AOF日志文件中,追加完毕后,立即使用新的AOF文件去替换旧的AOF文件。至此瘦身工作完成

RDB和AOF的抉择


RDB还是AOF?

(1) RDB模式的优缺点
优点:

  • RDB快照非常适合做数据备份,因为RDB数据文件非常的紧凑,同样的数据大小,相比AOF日志会更小,节省磁盘空间。
  • RDB快照恢复起来,也比AOF日志要更快

缺点:

  • 生成RDB文件,通常是遍历整个内存数据块,所以即使是fork的子进程去做,也需要一定的资源消耗,时间可能会比较长。
  • 因为生成一个完整的RDB快照数据文件,需要一定的时间,比如几分钟。而且在RDB快照生成期间,此时如果有数据的改动,是不再会反应到RDB文件中的,所以一但数据库宕机,就可能丢失几分钟的数据。既数据安全性没有AOF日志高
  • RDB数据文件,可读性差,不能直接打开

(2) AOF模式的优缺点
优点:

  • AOF支持三种持久化策略,可以使用fysnc函数强制内核刷新缓存到AOF日志中,相比RDB模式,AOF可以更好的保护数据不丢失。
  • AOF日志文件可读性好,可以使用文本工具直接打开,执行了什么修改指令, AOF日志就记录什么。方便查看

缺点:

  • 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大。
  • 通过AOF日志文件进行数据恢复所需要的时间会更长,相比RDB模式,宕机的Redis实例恢复时间过长,就需要耽搁一段时间

(3) 选择?!

  • 如果你只是需要一个数据备份,不太在乎一小部分数据的丢失,那么你可以使用RDB模式。并且RDB文件也便于迁移,放到多个实例中进行数据恢复
  • 如果你相比之下,更在乎数据的完整性,既更看重数据的不丢失,那么你就可以使用AOF模式

Redis4.0的混合持久化模式

我们知道,如果采用RDB持久策略,一旦Redis宕机,就有可能丢失大量数据。但如果我们使用AOF持久策略,使用AOF日志重放的方式,一旦AOF日志大了,想比RDB的恢复耗时就显的非常慢。所以为了解决这样纠结的问题,Redis 4.0 带来了一个新的持久化特性 “混合式持久化”

什么是混合式持久化策略?

  • 混合式持久化策略,就是不再是单独的RDB快照模式或AOF日志文件模式,而是同时采用RDB快照和AOF日志两种持久化模式的混合持久化模式。具体表现就是会将RDB文件内容和增量的AOF文件内容都存放在同一个文件中(同一个aof格式文件)。
  • 因为RDB快照数据和AOF日志数据都存在同一个文件中,所以该aof格式文件就不再只存放全量的AOF日志了,而是前面的大部分存储的是RDB的快照数据,后一小部分存储的是自RDB快照持久化开始到持久化结束的这段时间发生的增量AOF日志。所以通常是RDB数据占了大头,AOF日志只是一小部分的增量日志
  • 在混合式的持久化策略下,当宕机的Redis实例重启时,会先加载RDB快照的内容,然后再重放增量的AOF日志。这样就可以替代之前的RDB全量导入或AOF全量重放啦!因此重启效率也大幅提升
  • 通过配置参数aof-use-rdb-preamble就可以开启混合式持久化方式,可以通过 config get aof-use-rdb-preamble命令来检查

混合持久化策略的优缺点

  • 优点:结合了RDB和AOF的优点,使得数据恢复的效率大幅提升
  • 缺点:兼容性不好,redis-4.x新增,虽然最终的文件也是.aof格式的文件,但在4.0之前版本都不识别该aof文件,同时也因为前部分是RDB格式,阅读性很差。

运维最佳实践

  • 快照是通过fork子进程来执行的,它是一个比较耗时的操作,大块写磁盘会加重磁盘负载。默认持久化不建议只使用RDB的方式。建议使用AOF模式或混合式持久化模式
  • AOF的方式有三种策略,不建议在生产环境中使用always, 因为fysnc是一个耗时的IO操作,它会降低Redis的性能。也不建议使用no策略,因为让操作系统来决定如何同步磁盘,这时间可长可短,不安全,所以建议使用everysec策略
  • 在集群环境中,不建议使用Redis的主节点都进行持久化操作,而是放到其从节点进行。因为从节点是备份节点,一般没有来自客户端的请求压力,所以它的操作系统资源一般都比较充沛
  • 做完缓存集群的网络和资源监控检查,同时要注意一点的是,要给Redis实例所在的机子预留一部分的内存,用于给fork的子进程使用

其他问题


RDB持久化操作时,子进程拷贝父进程的数据副本用于持久化,不会增加内存消耗吗?

我们知道了解到,Redis的RDB持久化是基于多进程COW机制实现的。我们也简单的讲解了Copy On Write是什么。但是Redis的多进程COW机制也并非就仅仅是复制数据副本来生成数据快照这么简单的,之前说的这么简单,只是为了更容易理解

那么,那么复杂的情况是怎么个样子呢?

  • RDB持久化操作期间,由子进程在做数据持久化,子进程并不会修改现有的内存数据结构,它只是对内存数据结构进行遍历读取,然后序列化写到磁盘中。然而父进程则不同,因为即使持久化期间,也可能会多个客户端还在不断的向Redis发送读写请求,所以父进程需要不断的对内存数据结构进程修改和更新操作。

因为子进程生成的数据快照肯定是某个时间节点的,而父进程可能又在时刻改变子进程要扫描的内存数据结构,那怎么办呢?

  • COW机制中的父子进程并非是完全独立的进程,他们之间的关系更像是一个连体婴儿,虽然是两个进程,但是实际在子进程刚刚被fork出来的时候,父子进程实际共享的是同一段内存空间,就相当于有两个头,但却共享一个身体,不过之后还是会慢慢剥离的,前期只是为了尽量共享,节约资源。
  • 此时Redis,就会使用操作系统的COW机制进程数据段页面的分离(数据段是有很多操作系统的页面组合而成)。当父进程对其中一个页面的数据进行修改的时候,会将被共享的数据页面复制一份分离出来,然后对这个复制的页面进行修改。此时子进程想应的页面是没有变化的,依然是子进程产生时那一刻的数据
  • 随着父进程接收的写请求越来越多,要复制出来修改的共享页面也越来越多。所以此时内存就会持续增长,但是由于被修改数据的比例一般占Redis总数据的比例不会太大,所以内存也不会增加原来两倍的情况。当然如果你在持久化期间,全部数据都被修改了个遍,那么两倍内存还是有希望的。

为什么Redis的AOF机制是先成功命令才再记录日志?

这个问题的资料的确是有些少,Google, Baidu之后也不了了之。所以我只能引入@Hankoking的其中一个理由来回答这个问题了。

我个人觉得是有一定的道理的,但还是总感觉仅仅这个理由又似乎有些缺少份量,所以希望有明白大佬,可以解答一下个问题。


参考资料


文章推荐:

【Redis笔记】一起学习Redis | 聊聊Redis的持久化策略,AOF和RDB

【Redis笔记】一起学习Redis | 聊聊缓存,数据库的双写数据不一致问题

【Redis笔记】一起学习Redis | 从消息队列到PubSub模型

【Redis笔记】一起学习Redis | 如何应对缓存穿透,缓存雪崩?

【Redis笔记】一起学习Redis | 大海捞针,了解scan命令

发表评论

控制面板

您好,欢迎到访网站!

  查看权限