Redis AOF日志浅析

        redis之所以很快,是因为数据是存储在内存中的,直接从内存中读取和从磁盘上读取速度是完全不一样的。内存虽然很快,但是也有个不能忽视的问题,一旦服务器宕机,内存中的数据将全部丢失,所以redis数据的持久化是必须要考虑的问题。redis提供了两种持久化的机制,AOF(Append Only File)日志和RDB快照,下面浅析下AOF日志。

先写日志还是先把数据写到内存?

redis是先把数据写到内存,然后再记录日志。

为什么要先把数据记录到内存,然后再记录日志?这大概与redis机制有关系,redis针对AOF的写回定义了几种策略(Always,Everysec 和 No)像比如在No写回策略模式下,写回日志的时机是由操作系统决定什么时候写入的。redis写日志时也不会校验语法的合法性,如果先写日志,这样就会可能存在redis中记录的日志是不合法的redis语句的情况。

AOF中记录的内容?

其实AOF里面记录的是Redis收到的每一条命令,这些命令是以文本的形式保存的,redis实现了一套简单的协议,可以看博文。下面看下具体AOF日志文件中存储什么值。以Redis收到如下命令为例 "set testkey  testvalue"命令后记录的日志为例,看下AOF日志的内容

*3
$3
set
$7
testkey
$9
testvalue

第一行的 “ *3 ” 表示当前命令有三个部分,每个部分都是由 “$ + 数字 ” 开头,后面跟着具体的命令、键或者值,“数字” 表示这部分中的命令、键或者值一共有多少个字节,例如 “ $3 set ” 表示这部分有 3 个字节,其实就是 “ set ” 命令。

Redis先写内存再写入AOF日志两个潜在的风险

1、如果Redis执行完写入内存后服务器突然宕机了,日志还没来得及写入AOF日志,此时再重启时使用AOF恢复就会导致还未写入AOF的命令丢失,如果Redis是用作缓存还好,可以重新从数据库加载即可。但是如果Redis是直接用作数据库的话,此时因为命令还没有来得及记录日志,所以就无法用日志进行恢复了。

2、虽然采用写后日志避免了对当前命令的阻塞,但是可能会给下一个操作带来阻塞的风险,因为AOF日志也是再主线程中执行的,如果把日志写入磁盘时,磁盘的写压力比较大,就会导致写很慢,进而导致后续的操作都没法执行。

Redis的三种写回策略

其实也就是对应Redis配置文件中的配置项 appendfsync 的三个可选项。

  • Always:同步写回,每个命令一执行完,立马同步将日志写回磁盘。
  • Everysec:每秒写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区的内容写入磁盘
  • No:操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。

但是这三种写回策略都无法做到两全其美。

Always:可以做到基本不丢数据,但是它在每一个写命令后都有一个慢速的落盘操作,不可避免地会影响主线程性能;

No: 在写完缓冲区后,就可以继续执行后续的命令,但是落盘的时机已经不在 Redis 手中了,只要 AOF 记录没有写回磁盘,一旦宕机对应的数据就丢失了;

Everysec:采用一秒写回一次的频率,避免了“同步写回”的性能开销,虽然减少了对系统性能的影响,但是如果发生宕机,上一秒内未落盘的命令操作仍然会丢失。所以,这只能算是,在避免影响主线程性能和避免数据丢失两者间取了个折中。

但是,按照系统的性能需求选定了写回策略,并不是“高枕无忧”了。毕竟,AOF 是以文件的形式在记录接收到的所有写命令。随着接收的写命令越来越多,AOF 文件会越来越大。这也就意味着,我们一定要小心 AOF 文件过大带来的性能问题。这里的“性能问题”,主要在于以下三个方面:

  • 一是,文件系统本身对文件大小有限制,无法保存过大的文件;
  • 二是,如果文件太大,之后再往里面追加命令记录的话,效率也会变低;
  • 三是,如果发生宕机,AOF 中记录的命令要一个个被重新执行,用于故障恢复,如果日志文件太大,整个恢复过程就会非常缓慢,这就会影响到 Redis 的正常使用。

所以,我们就要采取一定的控制手段,这个时候,AOF 重写机制就登场了。

日志文件重写

随着系统的运行,日志文件会越来越大,因为采用AOF方式生成的日志文件是记录redis收到的每一条命令,我觉得这里应该只会存储对值由改变的相关命令。Redis 官网举了一个例子,如果对一个数执行了100次加 1(incr key) 操作,那其实会记录 100 次 incr key 日志文件,但是 key  经过100 次 incr 操作之后值变为100,其实只需要 记录 一句set key 100 就可以进行恢复了,Redis官网原文如下

不过,虽然AOF重写后,日志文件会缩小,但是要把整个数据库的最新数据的操作日志都写回磁盘,仍然是一个非常耗时的过程,这时,就需要关注另外一个问题了:重写会不会阻塞主线程?

AOF重写会阻塞主线程嘛?

重写过程是由后台子进程bgrewriteaof来完成的,这也是避免阻塞主线程,导致数据库性能下降。

Redis版本小于7.0.0时重写过程

重写过程总结为“一个拷贝,两处日志

“一个拷贝”就是指,每次执行重写时,Redis主进程fork出后台的bgrewriteaof子进程。此时fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里就包含了数据库最新的数据,然后bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。

注意:1、fork采用操作系统提供的写实复制(Copy On Write)机制,就是为了避免一次性拷贝大量内存数据给子进程造成的长时间阻塞问题,但fork子进程需要拷贝进程必要的数据结构,其中有一项就是拷贝内存页表(虚拟内存和物理内存的映射索引表),这个拷贝过程会消耗大量CPU资源,拷贝完成之前整个进程是会阻塞的,阻塞时间取决于整个实例的内存大小,实例越大,内存页表越大,fork阻塞时间越久。拷贝内存页表完成后,子进程与父进程指向相同的内存地址空间,也就是说此时虽然产生了子进程,但是并没有申请与父进程相同的内存大小。那什么时候父子进程才会真正内存分离呢?“写实复制”顾名思义,就是在写发生时,才真正拷贝内存真正的数据,这个过程中,父进程也可能会产生阻塞的风险。

2、fork出的子进程指向与父进程相同的内存地址空间,此时子进程就可以执行AOF重写,把内存中的所有数据写入到AOF文件中。但是此时父进程依旧是会有流量写入的,如果父进程操作的是一个已经存在的key,那么这个时候父进程就会真正拷贝这个key对应的内存数据,申请新的内存空间,这样逐渐地,父子进程内存数据开始分离,父子进程逐渐拥有各自独立的内存空间。因为内存分配是以页为单位进行分配的,默认4k,如果父进程此时操作的是一个bigkey,重新申请大块内存耗时会变长,可能会产阻塞风险。

“两处日志”又是什么呢?因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有写操作

第一处日志就是指正在使用的 AOF 日志,Redis 会把这个操作写到它的缓冲区。这样一来,即使宕机了,这个 AOF 日志的操作仍然是齐全的,可以用于恢复。

而第二处日志,就是指新的 AOF 重写日志。这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,我们就可以用新的 AOF 文件替代旧文件了。

                                AOF非阻塞重写过程(图来源于极客时间)

参考文件:Redis AOF原理_pythonluo的专栏-CSDN博客_redis的aof

04 | AOF日志:宕机了,Redis如何避免数据丢失?-极客时间

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值