本文已收录于专栏 《Redis从入门到进阶》

专栏前言

   本专栏开启,目的在于帮助大家更好的掌握学习Redis,同时也是为了记录我自己学习Redis的过程,将会从基础的数据类型开始记录,直到一些更多的应用,如缓存击穿还有分布式锁等。希望大家有问题也可以一起沟通,欢迎一起学习,对于专栏内容有错还望您可以及时指点,非常感谢大家 。

目录

  • 专栏前言
  • 1. Redis 的持久化
  • 2. AOF
  • 3.AOF的开启
  • 4.写回策略的选择
  • 5. AOF文件重写
  • 6. 后台重写

1. Redis 的持久化

   Redis的数据大家都知道是存储在内存中的,这也是它访问速度快的原因,但内存都有一个致命的缺点,如果不小心咔擦一下断电或者关机重启,那么内存当中的数据就全没了,Redis显然也逃不掉。
Redis的官方显然为了解决这个问题准备了方案,那就是进行持久化,将数据备份到硬盘当中,当重启的时候再从硬盘中获取数据,而官方提供有两种方案可选,一种是AOF,另一种叫RDB,两种方法各有优劣,具体的选择还得看应用场景。

2. AOF

  AOF全程叫做 Append Only File(追加文件),Redis中每一个写的命令都会被它记录在AOF文件中,可以看做是一个命令日志文件,注意读的指令不会记录,因为对数据持久化没有意义。每当Redis重启后,就可以读取AOF文件的操作指令并执行,以此来达到恢复数据的目的

3.AOF的开启

  在RedisAOF模式是默认不开启的,我们需要去redis.config中修改参数将其开启:

当我们的AOP开启后,指令就会默认存储到这个appendonly.aof的文件中。当然从前面的流程图可以看出
我们的Redis是先执行命令,再将命令写入AOF文件中的。这样有什么好处呢?

  • 1 . 避免额外检查开销
    因为我们是先执行命令后写日志,这样错误的命令就不会被写入到日志中,也就节省了语法检查的开销。

  • 2 .不阻塞执行命令
    写磁盘文件是一个比较耗时的操作,如果都先写入磁盘再执行命令,那么大量的命令就会被阻塞住,影响效率。而先执行命令再写,就可以异步来进行写操作,不会影响Redis的执行效率。

  而Redis也确实是如此做的, 我们的指令会先以某种特定的协议格式(RESP协议,Redis客户端和服务器交互的协议)写入到服务器的aof_buf缓冲区当中,而AOF会根据某种策略把缓冲区中的指令同步向磁盘。

  当然上述操作也并不是没有弊端,它存在以下缺点:

    1. 数据丢失风险
      因为是先执行指令,再写入缓冲区,显然如果Redis还没来得及写入磁盘服务器就宕机了,那么这个数据就会丢失了。
    1. 阻塞下一条指令
      假设第一条指令进来,先执行,执行完毕后再将指令写入磁盘的话,如果服务器I/O特别大,此时写入磁盘特别慢,那这时候再来第二条指令就会被阻塞住了。因为这两种操作都是主线程来进行的。

4.写回策略的选择

  这时有一个问题我们必须思考,究竟什么时候把缓冲区的指令同步到磁盘上呢?如果太频繁,大量的I/O会影响服务器性能,如果同步太晚,服务器宕机又怕丢失大量的数据,所以Redis提供了三个等级的写回策略供我们选择,分别是——always、everysec、no。

写回策略同步频率优点缺点
always每个指令同步写入可靠性高,不易丢失数据性能开销大
everysec每秒同步一次性能适中宕机时会丢失1s内的数据
no让操作系统来决定何时同步性能高宕机时丢失数据多,不可控

Redis默认采用的是everysec这种方案。三者同步的实现,就是在底层调用一个名叫fsync()函数的时间,always 同步执行,everysec 异步执行,no永不执行。

5. AOF文件重写

  AOF是一个文件,那么随着指令执行的越来越多,文件存储的东西也越来越多,文件就会变得很大。当文件过大时肯定会带来各种各样的问题,最主要的肯定是影响性能,我们Redis重启时,如果文件太大,读取的速度就会特别慢。
 所以Redis为我们提供了 AOF重写机制 。 当文件的大小超过某个阈值时,就会触发重写机制,或者手动调用bgrewriteaof命令触发,来压缩 AOF 文件。那么它重写的依据是什么呢?
 我们都知道Redis是一个key-value的键值型数据库,假设我们下面这些指令

set name zhangsanset name lisiset name wangwu

那么Redis只会存储最后一条指令:

set name wangwu

因为我们只需要关心Redis最终的效果,对于历史记录都会被覆盖,所以我们无需去记载。还有一些操作我们也可以进行合并起来,比如下面:

set num 123set name jackset num 666

可以合并为一条指令来执行,用最少的指令达到相同的效果

mset name jack num 666

还有一点需要注意的是,在重写时我们会拷贝一份AOF文件来进行重写,这样如果重写过程中失败了,也不会对原AOF文件造成污染,重写成功后直接覆盖原AOF文件即可。

6. 后台重写

  当触发重写时,AOF文件已经较大了,对所有数据重写这一个操作是非常非常耗时的,如果直接在主进程中进行,那么就会导致此时Redis无法去执行新到来的命令,所以我们重写AOF的过程是由后台子进程来完成的。
 这样操作有两个好处:

    1. 子进行重写AOF,主进程可以继续执行指令,避免主线程阻塞。
    1. 子进程带有主进程的数据副本,使用子进程而非线程,可以避免在不需要加锁的情况下,保证数据的安全性

  有一个需要注意的问题就是,子进程在重写时,主线程仍然在接收新的指令,会对Redis数据库进行修改,从而可能会出现Redis和重写后的AOF文件数据不一致的情况。所以Redis还提供了一个AOF重写缓冲区,这个缓冲区会在主线程fork出子进程开始工作,此时主线程在接收到客户端的命令后,不仅在AOF缓冲区记录日志,也会在重写缓冲区记录。

  当子线程完成重写后,就会向主线程附送一个信号告诉它,这时主线程就会调用一个信号的处理函数,该函数会干两件事:

    1. AOF重写缓冲区的所有内容追加到AOF缓冲区中,使得两个AOF文件保存的数据库状态一致
    1. 新的AOF文件改名,覆盖原来的AOF文件

然后主线程就可以继续执行命令了,在整个 AOF 后台重写的过程中,只有发生写时复制和信号处理函数时会对主线程进行阻塞, 其余时候,主线程都不会阻塞。