宕机了,Redis如何避免数据丢失?

  • Post author:
  • Post category:其他


Redis的持久化主要有两大机制,即AOF日志和RDB快照

😀AOF日志

1.2 AOF日志是如何实现的?

说到⽇志,我们⽐较熟悉的是数据库的写前⽇志(Write Ahead Log, WAL)——例如mysql的redolog,也就是说,在实际写数据前,先把修改的数据记到⽇志⽂件中,以便故障时进⾏恢复。不过,AOF⽇志正好相反,它是写后⽇志,“写后”的意思是Redis是先执⾏命令,把数据写⼊内存,然后才记录⽇志,如下图所示:

那AOF为什么要先执⾏命令再记⽇志呢?

1、AOF⾥记录的是Redis收到的每⼀条命令,这些命令是以⽂本形式保存的。

2、为了避免额外的检查开销,Redis在向AOF⾥⾯记录⽇志的时候,并不会先去对这些命令进⾏语法检查。所以,如果先记⽇志再执⾏命令的话,⽇志中就有可能记录了错误的命令,Redis在使⽤⽇志恢复数据时,就可能会出错。

3、它是在命令执⾏后才记录⽇志,所以不会阻塞当前的写操作。

AOF两个潜在的⻛险

1、如果刚执⾏完⼀个命令,还没有来得及记⽇志就宕机了,那么这个命令和相应的数据就有丢失的⻛险。

2、AOF虽然避免了对当前命令的阻塞,但可能会给下⼀个操作带来阻塞⻛险。这是因为,AOF⽇志也是在主线程中执⾏的,如果在把⽇志⽂件写⼊磁盘时,磁盘写压⼒⼤,就会导致写盘很慢,进⽽导致后续的操作也⽆法执⾏了。

基于以上两点,如果我们能够控制⼀个写命令执⾏完后AOF⽇志写回磁盘的时机,这两个⻛险就解除了。

  • Always,同步写回:每个写命令执⾏完,⽴⻢同步地将⽇志写回磁盘;
  • Everysec,每秒写回:每个写命令执⾏完,只是先把⽇志写到AOF⽂件的内存缓冲区,每隔⼀秒把缓冲区中的内容写⼊磁盘;
  • No,操作系统控制的写回:每个写命令执⾏完,只是先把⽇志写到AOF⽂件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。

AOF文档太大了怎么办?

随着输入的命令越来越多,这些命令都会被记录到AOF文件中,导致AOF文件过大。

AOF文件过大的问题:

系统发送宕机重启,从AOF文件恢复数据的速度很慢,影响业务。

占用系统磁盘

redis有AOF重写机制,当⼀个键值对被多条写命令反复修改时,AOF⽂件会记录相应的多条命令。但是,在重写的时候,是根据这个键值对当前的最新状态,为它⽣成对应的写⼊命令。

和AOF⽇志由主线程写回不同,重写过程是由后台线程bgrewriteaof来完成的,这也是为了避免阻塞主线程,导致数据库性能下降。

我把重写的过程总结为“⼀个拷⻉,两处⽇志”。

  • “⼀个拷⻉”就是指,每次执⾏重写时,主线程fork出后台的bgrewriteaof⼦进程。此时,fork会把主线程的内存拷⻉⼀份给bgrewriteaof⼦进程,这⾥⾯就包含了数据库的最新数据。然后,bgrewriteaof⼦进程就可以在不影响主线程的情况下,逐⼀把拷⻉的数据写成操作,记⼊重写⽇志。
  • “两处⽇志”⼜是什么呢?因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有写操作,第⼀处⽇志就是指正在使⽤的AOF⽇志,Redis会把这个操作写到它的缓冲区。这样⼀来,即使宕机了,这个AOF⽇志的操作仍然是⻬全的,可以⽤于恢复。⽽第⼆处⽇志,就是指新的AOF重写⽇志。这个操作也会被写到重写⽇志的缓冲区。这样,重写⽇志也不会丢失最新的操作。等到拷⻉数据的所有操作记录重写完成后,重写⽇志记录的这些最新操作也会写⼊新的AOF⽂件,以保证数据库最新状态的记录。此时,我们就可以⽤新的AOF⽂件替代旧⽂件了。

Redis采⽤fork⼦进程重写AOF⽂件时,潜在的阻塞⻛险?

1:、fork⼦进程,fork这个瞬间⼀定是会阻塞主线程的(注意,fork时并不会⼀次性拷⻉所有内存数据给⼦进程),fork采⽤操作系统提供的写实复制(Copy On Write)机制,就是为了避免⼀次性拷⻉⼤量内存数据给⼦进程造成的⻓时间阻塞问题,但fork⼦进程需要拷⻉进程必要的数据结构,其中有⼀项就是拷⻉内存⻚表(虚拟内存和物理内存的映射索引表),这个拷⻉过程会消耗⼤量CPU资源,拷⻉完成之前整个进程是会阻塞的,阻塞时间取决于整个实例的内存⼤⼩,实例越⼤,内存⻚表越⼤,fork阻塞时间越久。拷⻉内存⻚表完成后,⼦进程与⽗进程指向相同的内存地址空间,也就是说此时虽然产⽣了⼦进程,但是并没有申请与⽗进程相同的内存⼤⼩。那什么时候⽗⼦进程才会真正内存分离呢?“写实复制”顾名思义,就是在写发⽣时,才真正拷⻉内存真正的数据,这个过程中,⽗进程也可能会产⽣阻塞的⻛险,就是下⾯介绍的场景。

fork出的⼦进程指向与⽗进程相同的内存地址空间,此时⼦进程就可以执⾏AOF重写,把内存中的所有数据写⼊到AOF⽂件中。但是此时⽗进程依旧是会有流量写⼊的,如果⽗进程操作的是⼀个已经存在的key,那么这个时候⽗进程就会真正拷⻉这个key对应的内存数据,申请新的内存空间,这样逐渐地,⽗⼦进程内存数据开始分离,⽗⼦进程逐渐拥有各⾃独⽴的内存空间。因为内存分配是以⻚为单位进⾏分配的,默认4k,如果⽗进程此时操作的是⼀个bigkey,重新申请⼤块内存耗时会变⻓,可能会产阻塞⻛险。另外,如果操作系统开启了内存⼤⻚机制(Huge Page,⻚⾯⼤⼩2M),那么⽗进程申请内存时阻塞的概率将会⼤⼤提⾼,所以在Redis机器上需要关闭Huge Page机制。Redis每次fork⽣成RDB或AOF重写完成后,都可以在Redis log中看到⽗进程重新申请了多⼤的内存空间。

AOF重写为什么新创建一个AOF日志文件,为什么不复用老的AOF文件?

  • AOF重写不复⽤AOF本⾝的⽇志,⼀个原因是⽗⼦进程写同⼀个⽂件必然会产⽣竞争问题,控制竞争就意味着会影响⽗进程的性能。
  • 如果AOF重写过程中失败了,那么原本的AOF⽂件相当于被污染了,⽆法做恢复使⽤。所以Redis AOF重写⼀个新⽂件,重写失败的话,直接删除这个⽂件就好了,不会对原先的AOF⽂件产⽣影响。等重写完成之后,直接替换旧⽂件即可。

🤣内存快照—RDB

AOF记录的是操作命令,⽽不是实际的数据,所以,⽤AOF⽅法进⾏故障恢复的时候,需要逐⼀把操作⽇志都执⾏⼀遍。如果操作⽇志⾮常多,Redis就会恢复得很缓慢,影响到正常使⽤。

基于以上原因,redis有另外一套机制保持内存数据——内存快照

对Redis来说,它实现类似照⽚记录效果的⽅式,就是把某⼀时刻的状态以⽂件的形式写到磁盘上,也就是快照。这样⼀来,即使宕机,快照⽂件也不会丢失,数据的可靠性也就得到了保证。这个快照⽂件就称为RDB⽂件。

会阻塞主线程吗?

Redis提供了两个命令来⽣成RDB⽂件,分别是save和bgsave。

  • save:在主线程中执⾏,会导致阻塞;
  • bgsave:创建⼀个⼦进程,专⻔⽤于写⼊RDB⽂件,避免了主线程的阻塞,这也是Redis RDB⽂件⽣成的默认配置。

快照时数据能修改吗?

  • 如果快照执⾏期间数据不能被修改,是会有潜在问题的。在快照的Ns时间⾥,如果数据都不能被修改,Redis就不能处理对这些数据的写操作,那⽆疑就会给业务服务造成巨⼤的影响。
  • 如果使用bgsave命令,此时,主线程的确没有阻塞,可以正常接收请求,但是,为了保证快照完整性,它只能处理读操作,因为不能修改正在执⾏快照的数据。为了快照⽽暂停写操作,肯定是不能接受的。所以这个时候,Redis就会借助操作系统提供的写时复制技术(Copy-On-Write, COW)【当父线程操作一个已经存在的key时,会复制内存数据,另外开辟一块内存空间,当完成命令操作时,地址指向这个新的内存空间】,在执⾏快照的同时,正常处理写操作。

Redis 4.0中提出了⼀个混合使⽤AOF⽇志和内存快照的⽅法。简单来说,内存快照以⼀定的频率执⾏,在两次快照之间,使⽤AOF⽇志记录这期间的所有命令操作。这样⼀来,快照不⽤很频繁地执⾏,这就避免了频繁fork对主线程的影响。⽽且,AOF⽇志也只⽤记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现⽂件过⼤的情况了,也可以避免重写开销。



版权声明:本文为m0_47704296原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。