本文已收录于专栏 《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的开启
在Redis
内 AOF
模式是默认不开启的,我们需要去redis.config
中修改参数将其开启:
当我们的AOP
开启后,指令就会默认存储到这个appendonly.aof
的文件中。当然从前面的流程图可以看出
我们的Redis
是先执行命令,再将命令写入AOF
文件中的。这样有什么好处呢?
1 . 避免额外检查开销
因为我们是先执行命令后写日志,这样错误的命令就不会被写入到日志中,也就节省了语法检查的开销。2 .不阻塞执行命令
写磁盘文件是一个比较耗时的操作,如果都先写入磁盘再执行命令,那么大量的命令就会被阻塞住,影响效率。而先执行命令再写,就可以异步来进行写操作,不会影响Redis
的执行效率。
而Redis
也确实是如此做的, 我们的指令会先以某种特定的协议格式(RESP
协议,Redis
客户端和服务器交互的协议)写入到服务器的aof_buf
缓冲区当中,而AOF
会根据某种策略把缓冲区中的指令同步向磁盘。
当然上述操作也并不是没有弊端,它存在以下缺点:
- 数据丢失风险
因为是先执行指令,再写入缓冲区,显然如果Redis
还没来得及写入磁盘服务器就宕机了,那么这个数据就会丢失了。
- 数据丢失风险
- 阻塞下一条指令
假设第一条指令进来,先执行,执行完毕后再将指令写入磁盘的话,如果服务器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的过程是由后台子进程来完成的。
这样操作有两个好处:
- 子进行重写
AOF
,主进程可以继续执行指令,避免主线程阻塞。
- 子进行重写
- 子进程带有主进程的数据副本,使用子进程而非线程,可以避免在不需要加锁的情况下,保证数据的安全性。
有一个需要注意的问题就是,子进程在重写时,主线程仍然在接收新的指令,会对Redis
数据库进行修改,从而可能会出现Redis
和重写后的AOF
文件数据不一致的情况。所以Redis
还提供了一个AOF
重写缓冲区,这个缓冲区会在主线程fork
出子进程开始工作,此时主线程在接收到客户端的命令后,不仅在AOF
缓冲区记录日志,也会在重写缓冲区记录。
当子线程完成重写后,就会向主线程附送一个信号告诉它,这时主线程就会调用一个信号的处理函数,该函数会干两件事:
- 将
AOF
重写缓冲区的所有内容追加到AOF
缓冲区中,使得两个AOF
文件保存的数据库状态一致
- 将
- 新的
AOF
文件改名,覆盖原来的AOF
文件
- 新的
然后主线程就可以继续执行命令了,在整个 AOF
后台重写的过程中,只有发生写时复制和信号处理函数时会对主线程进行阻塞, 其余时候,主线程都不会阻塞。