对于正常的redis使用,如果redis中存放了很重要的数据,并且一旦redis数据丢失的情况下,就需要重新恢复数据。一般情况最容易解决的方法是:从数据库中读取数据再set进缓存中。但是这样的解决方式也有很大的弊端:比如对于数据库:需要频繁访问数据库,会给数据库带来很大的压力。
对于redis来说,也提供了两种持久化机制,来保证我们从日志中来恢复redis数据。
reids的持久化机制主要是AOF日志和RDB快照日志。这篇文章主要学习下AOF日志。
一、AOF日志是如何实现的?
先看下AOF日志中存放的内容。
如上图所示,我们这条命令是set key value,这条命令就会分成以下几个部分被保存在aof文件中。
(1)*3,表示这条命令有三个部分。
(2)$3 set ,表示第一个命令是set,并且占了3个字节。
(3)$3 key,表示key是key,并且占了3个字节。
(4)$5 value,表示value是value,并且占了5个字节。
一条命令就会被分割成以上几个部分,存放在aof文件中。为了避免检查aof日志中的命令而导致redis命令报错,一般情况下aof日志记录命令都是在redis命令执行之后放入的。这样操作的好处:
(1)避免了命令执行中额外的检查开销。
(2)先执行命令的好处在于只有命令执行成功才会被记录到日志中,避免出现aof日志中存在命令,而redis本身操作这条命令失败的情况。
(3)先执行命令,保证了不会在aof日志中记录错误的命令,防止在使用aof日志恢复数据时出错的情况。因为redis先执行命令,如果命令有问题就直接返回操作失败给客户端,并不会操作aof日志。
这样操作的风险:
(1)如果刚执行完一个命令,立马就宕机了,会导致这条命令没有被记录到aof日志中,就会丢失了这条命令。所以如果将redis功能作为数据库来使用的话会有比较严重的数据丢失问题。
(2)如果写命令很多,会导致aof日志写入磁盘时的压力变大。
二、AOF的三种写入策略
为了解决上述风险中的第二个问题,aof提供了三种写入策略,可在aof配置项的appendfsync中选择。
(1)Always - 同步写回
每条写命令执行完毕,立马同步的将日志同步到磁盘。
(2)Eversec - 每秒写回
每个写命令执行完毕,只是先把日志写到aof文件的内存缓冲区。每隔一秒把缓冲区中的内容写入磁盘。
(3)No - 操作系统控制写回
每个写命令执行完毕,只是先把日志写到aof文件的内存缓冲区。操作系统自行决定何时将缓冲区内容写入到磁盘。
三种选择的性能比较:
(1)Always - 同步写回
同步写回虽然可以做到数据的不丢失,但是在每一个写命令之后都有一个同步的写入磁盘操作,会影响redis主线程性能。
(2)Eversec - 每秒写回
每秒写回策略,在写命令时,只是将写命令的aof写到缓冲区就可返回,不会影响redis主线程性能。缺点是,写入磁盘的时机不由redis主线程把控,一旦aof记录没有写到磁盘,发生宕机,对应的数据就会丢失。
(3)No - 操作系统控制写回
操作系统写回的策略与每秒写回类似,区别在于每秒写回是一秒写入一次磁盘,操作系统写回磁盘的时间无法控制,如果发生宕机每秒写回可能只是丢失一秒的数据,而操作系统控制写回丢失的数据会更多。
综上所述:always策略属于同步写回,可靠性高,数据基本不丢失。但是每个写命令都要入磁盘,性能影响较大。
eversec策略属于每秒写回,性能适中。但是宕机会丢失1秒内未写入磁盘的数据。
no策略属于操作系统控制写回,性能最好。但是宕机时数据丢失最多。
所以想要高性能可以选择No策略,如果需要数据高可靠性就选择always策略。如果允许数据有一点丢失,又不希望性能有太大影响就选择eversec策略。
三、AOF重写机制
AOF主要是以文件的形式来接收所有的写命令,因此随着写命令的增多,aof文件会越来越大。文件越来越大也是我们需要考虑的因素,因为他会对性能造成以下的问题:
(1)aof文件本身就是有大小限制的,所以不可以无限制的存储所有的命令。
(2)文件过大,继续增加写命令的话,效率也会变低。
(3)宕机后想要根据这个aof来恢复数据的话,由于aof文件很大会导致恢复的过程变慢,影响redis的恢复。
所以当aof文件过大的情况下,引入了aof重写机制。
aof重写机制就是在重写时,redis会创建一个新的aof文件。即原始的aof文件是追加的命令存储在aof中,比如对同一key,做了三次修改命令,原始的aof就会有三条记录,而重写机制只会保留三条合并后的一条命令。
举例:
1、String的修改操作
set testKey "1";
set testKey "2";
set testKey "100";
重写前的aof会有三条记录,而重写的aof只有合并后的一条记录即set testKey "100"。
2、List的修改操作
LPUSH testList "A","B";
LPUSH testList "C","D";
RPOP testList;
RPOP testList;
LPUSH testList "E","F";
重写前的aof会有5条命令,而重写后的aof只有合并后的一条记录即LPUSH testList "F","E","D","C";
这样一来在对redis宕机后恢复数据时,根据重写后的aof来进行恢复时,执行的命令就少了很多,提高了恢复数据的效率。
四、AOF重写工作原理
为了保证AOF重写时,不阻塞redis的主线程,他采用了“一个拷贝,两处日志”的方式。首先aof重写的工作是由后台子进程bgrewriteof来完成的。
“一个拷贝”指,每次执行重写工作,主线程会fork出bgrewriteof子进程,并且在这一步也会把主线程中的最新数据拷贝给bgrewriteof。
“两处日志”:第一处指原始的aof日志,因为此时的redis还是在运行的,如果有新的写请求被执行,那么还是会追加的形式放到原始的aof日志或aof日志缓冲区中。第二处指新的aof重写日志,等到所有拷贝数据的所有更新记录重写完成后,重写日志记录的日志也会变为新的aof日志,取代原始重写前的aof日志。
工作原理图:
综上所述,每次aof重写时,都会使用一个数据拷贝来用于重写,然后使用两个日志来保证在重写过程中新写入的数据不会丢失。并且整体操作采用子进程的方式来操作,并不会阻塞主线程。
五、总结
redis避免数据丢失的其中一种持久化方式是AOF日志,通过逐一记录更新命令的方式写入到aof日志中进行持久化。如果出现宕机需要恢复数据时就根据aof日志逐一执行命令来恢复数据。
aof日志的写入方式redis提供了三种方式,总结如下:
想要高性能可以选择No策略,如果需要数据高可靠性就选择always策略。如果允许数据有一点丢失,又不希望性能有太大影响就选择eversec策略。
另外为了防止出现日志文件过大造成的性能问题,redis也提供了aof重写机制,根据redis中的最新数据将多个命令合并成一条命令。重写机制也采用了从主线程中fork出一个bgrewriteaof子进程的方式来进行,防止阻塞redis主线程。
问题一:AOF日志重写的时候,是由bgrewriteaof子进程来完成的,不用主线程参与。但是这个过程有没有潜在的阻塞风险呢?
(1)主线程将数据拷贝给子进程时会阻塞主线程。redis实例内存越大,fork执行时间就会越长。
(2)bgrewriteaof子进程和主线程是共享内存的。当主线程收到新的更新操作时,主线程会申请新的内存空间用来保存更新的数据,如果操作的是bigkey,那么主线程会因为申请大空间而面临阻塞风险,因为操作系统在分配内存空间时,有查找和锁的开销,会导致阻塞。
问题二:AOF重写为什么不在原始aof日志中。
如果都用aof重写前日志的话,由于主线程要写入日志,bgrewriteof子线程也要写入日志,这两者就会竞争文件系统的锁,会影响主线程的性能。
问题三:主线程、子进程和后台线程的联系与区别?
从操作系统的角度来看,进程一般是指资源分配单元,例如一个进程拥有自己的堆、栈、虚拟空间(页表)、文件描述符等。
线程一般是指CPU进行调度和执行的实体。
主进程/主线程指,如果一个进程启动后,没有再创建额外的线程,那么这样的进程就被称为主进程或主线程。
综上所述,以redis为例,redis本身就是一个进程,但是redis也没有再依赖其他的线程,主要的工作比如接收客户端请求、处理写操作请求都在redis中。所以redis本身这个进程是完成主要的工作,所以可以叫做主进程或主线程。
但是在主线程中,我们可以通过fork操作来创建子进程,比如上文提到的bgrewriteaof子进程就是通过fork创建出来的。
而后台线程,是指相对于完成主要工作的主线程来说的,例如异步删除任务等线程操作,可以称为后台线程。redis可通过pthread_create来创建线程。
问题四:对于重写过程中,新的写入数据如果更新到重写后的aof日志中?
aof的工作原理如下:
(1)redis主线程执行fork操作来创建bgrewriteaof子进程,并拷贝了主线程中最新的内存数据。
(2)子进程根据拷贝后的内存数据写入到临时文件中。
(3)对于工作中所有新的写入命令,主线程会将他们累积到一个内存缓存中,一边将新的命令写入到原始的aof日志中,保证原始的aof日志和新的写入请求数据不丢失。
(4)重写工作完成,会给主线程发送一个信号,主线程收到信号后将内存缓存中的所有数据追加到新的aof日志中。
(5)之后所有的命令都会追加到新的aof日志中。
最后,redis持久化的方式并不能完全只靠aof一种持久化方式,下一篇我们来学习下另一种持久化方式,rdb快照日志。