我们知道,Redis是一个基于内存的缓存数据库,但是基于内存就有个问题,服务器一down机,内存中的数据就会丢失。这时候,不论我们把Redis作为缓存还是数据库使用,是不可接受的。所以我们需要把数据落到磁盘上,做数据的持久化。那redis持久化的方式有哪些呢?且看我们往下分解。
RDB,英文全称是Redis DataBase,是一种基于内存快照的持久化方式。内存快照是指内存数据在某一时刻的状态记录。正是由于RDB记录的是某一时刻的数据,所以在进行数据恢复的时候,我们可以直接把数据读到内存,很快完成恢复。
Redis使用RDB的方式进行数据持久化,是进行全量数据的处理,即全量快照。它有两种命令方式:save和bgsave。这两种方式如下:
save 900 1
save 300 10
save 60 10000
所以我就以为redis内存默认会自动采用save的方式进行持久化,其实这是一个理解上的错误。上面配置文件中的save配置,其实是用来说明进行RDB快照的时机,而不是说save命令。
我们知道,使用bgsave的时候,主进程会fork出一个子进程用于RDB数据的写入,这时候主进程是没有阻塞的。那我们可能会说,那redis肯定能够进行读写啊,因为redis的主进程没有阻塞啊。其实这是一个误区,阻塞和读写是两个概念,两者没有什么大的相关性。不阻塞不一定就能够读写,因为我们读写,最终是要得到或者更新数据,与阻塞和非阻塞没有什么太大关系。
bgsave子进程,只是从主进程fork出的一个进程,此时bgsave子进程会共享主进程的所有内存数据。但这样在进行数据写操作时可能就会比较麻烦,因为两个进程共享一个内存数据,那么就会产生并发,内存数据就会有竞争,处理不好就会产生数据的不一致。那怎么结局这个问题呢?其实Redis借助了操作系统的写时复制(Copy On Write)机制,在bgsave子进程执行快照的时候,主进程正常进行读写。在bgsave子进程执行过程中,此时如果主进程进行读,由于二者是单独的进程,所以读没有问题;如果主进程写的时候,不是新增操作,而是一个修改操作,那么这时候就会对该修改的数据生成一小块内存副本,子进程把内存副本写入的RDB,而主进程仍然可以正常的操作原来的那一小块数据内存。
我们先考虑一下,利用bgsave子进程来进行快照处理,一旦redis发生down机事故,恢复时是否能够完全进行数据恢复而没有丢失么?
们先在 t0 时刻做了一次快照,然后又在 t2时刻做了一次快照,在这期间的t1时刻,数据块 5 和 6 被修改了。如果在 t1和t2 这段时间内,机器宕机了,那么,只能按照 t0 时刻的快照进行恢复。此时,数据块 5 和 96的修改值因为没有快照记录,就无法恢复了。所以,我们如果使得t2和t0之间的时间间隔足够足够小,就能够在很大程度上减少数据的丢失(只能减少,不可能完全避免)。那么设置成足够小会有问题么?由于bgsave是一个单独的子进程,貌似是没有问题的,但其实问题没有这么简单,为什么的?虽然 bgsave 执行时不阻塞主进程,但是,如果频繁地执行全量快照,也会带来两方面的开销。
从上面的知识我们知道bgsave子进程无法实现数据的完全不丢失,所以根据以前的知识,我们可以做数据增量,做增量快照,这也是很多数据库中采取的一种方式。增量快照,就是指,做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。
假如在t0时刻做了一次全量快照,t1、t2、t3时刻对数据进行了修改,此时就要开辟一个内存空间做一个修改记录的存储,假设有瞬间有10万个数据修改,那么可想而知,此时的内存空间会是不小的开销,得不偿失。
Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主进程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。
这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势,
关于AOF,我们下面会详细介绍。
在第二部分我们主要介绍了RDB,基于内存全量快照的数据持久化,这一部分我们介绍第二种持久化的方式:AOF。
AOF的英文全称是Append Only File,是redis使用日志的方式来持久化数据的一种方式。AOF采用“写后日志”的方式,记录redis的操作命令(注意:aof记录的并不是内存中的全量数据,而是redis一条一条的执行命令)。相比我们之前熟悉的一些数据库,特别是mysql这种关系型数据库,都是采用“写前日志”的方式,那么redis为什么采用“写后日志”呢?
通过上面的叙述可以知道,AOF日志中记录的是一条一条的的执行命令,而AOF记录这些命令的时候,会先检测命令的正确性(比如语法、语义)等,所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。而采用写后日志的方式,就会让redis主进程先执行命令,执行成功后才写入AOF日志,否则就会向客户端返回错误,所以Redis 使用写后日志这一方式的一大好处是,可以避免出现记录错误命令的情况。除此之外,采用“写后日志”,可以避免阻塞当前写操作。
AOF日志可能产生的两个问题:
配置参数 | 解释 | 优点 | 缺点 |
---|---|---|---|
Always | 同步写回 | 数据可靠性高,基本不会丢失 | 每个写命令都要落到磁盘,极大影响性能 |
Everysec | 每秒写回 | 基本可以保证大部分数据不会丢失,性能也不会影响过大 | 宕机时会丢失1秒的数据 |
No | 操作系统控制的写回 | 性能比较好 | 宕机时丢失的数据会比较多 |
我们可以根据自己项目对高性能和高可靠性的要求,来选择使用哪种写回策略了想要获得高性能,就选择 No 策略;如果想要得到高可靠性保证,就选择 Always 策略;如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择 Everysec 策略。
什么是AOF的重写机制?为什么要进行AOF的重写。其实这还是要从性能角度来考虑。因为AOF是采用写日志的方式,将命令追加到文件中,随着我们的使用时间越长,执行的命令越多,自然就会导致我们的AOF日志的大小逐渐变大。众所周知,一个文件变大,那么在对文件进行先关操作时,就会产生文件操作的性能问题,主要表现的以下三个方面:
AOF的重写过程是由主进程fork出一个bgrewriteaof子进程,单独用于AOF日志的重写,这样做的好处是不阻塞主进程,这个RDB中的bgsave有异曲同工之处。
每次 AOF 重写时,Redis 会先执行一个内存拷贝(其实开始是内存共享,然后根据主进程的写命令,做Copy-On-Write),用于重写;然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。而且,因为 Redis 采用额外的进程进行数据重写,所以,这个过程并不会阻塞主进程。
上面我们介绍了AOF重写的过程,那除了我们手动执行bgrewriteaof命令进行AOF日志的重写,redis本身是在什么条件下触发自动重写呢?
如果要用redis的自动触发,首先也是最重要的,AOF的功能要开启,配置为:appendonlyfile yes。
在AOF功能开启的情况下,AOF自动重写条件主要涉及到下面三个变量:
Redis采用fork子进程重写AOF文件时,潜在的阻塞风险包括:fork子进程 和 AOF重写过程中父进程产生写入的场景。
AOF重写不复用AOF本身的日志,一个原因是父子进程写同一个文件必然会产生竞争问题,控制竞争就意味着会影响父进程的性能。二是如果AOF重写过程中失败了,那么原本的AOF文件相当于被污染了,无法做恢复使用。所以Redis AOF重写一个新文件,重写失败的话,直接删除这个文件就好了,不会对原先的AOF文件产生影响。等重写完成之后,直接替换旧文件即可。
通过上面对RDB和AOF两种redis数据持久化的阐述,我们可以看出,两者各有优劣。那么在我们的系统中应该怎么选择呢?
这里尽给出自己的一些建议: