🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
- 1. 持久化概述
- 2. 持久化的策略
- 2.1 RDB模式
- 2.1.1 持久化策略
- 2.1.2 触发机制
- 2.2 AOF模式
- 2.2.1 持久化策略
- 2.2.2 重写机制
- 2.2.3 触发条件
- 3. 混合持久化
1. 持久化概述
我们之前在学习MySQL数据库的时候,我们学习过MySQL事务的四个比较核心的特性:分别是原子性(把对多个表的操作打包为一个操作),一致性(事务前后的数据都是合法数据,比如同一个操作中同时减少和增加一个值,要不同时成功,要不同时失败,不能出现中间状态),持久性(把数据存储在硬盘上),隔离性(脏读,不可重复读,幻读,串行化).
我们知道,我们学习的redis,是把数据存在内存中的,但是我们知道,数据存储在内存中的数据是不持久的,但是redis为了持久化,数据还是要想办法保存在硬盘上的.其中硬盘上的数据只是在redis重启的时候,用来恢复数据使用的.
当我们要插入一个数据的时候,就需要把这个数据同时写入到内存和硬盘中.但是在写硬盘的时候,具有不同的策略.
2. 持久化的策略
Redis在实现持久化的时候,具体是按照以下两种方式来实现的,一种是RDB(Redis Database)模式,一种是AOF(Append Only File)模式.其中的RDB采用的备份模式是定期备份,也就是每隔一段时间,就会把内存中的数据备份到硬盘中.而AOF使用的是实时备份,只要内存中出现了新的数据,就会立即备份到硬盘中.
2.1 RDB模式
2.1.1 持久化策略
RDB在硬盘中备份数据的时候,采用的是生成"快照"的方式.
何为生成快照,就像警察来到案发现场一样,对没有破坏的案发现场进行拍照,按照照片上提供的线索对现场当时的情况进行还原.
Redis生成快照的方式也是类似的,Redis在内存中存储数据之后,Redis会给这些数据进行"拍照",生成一个文件,存储在硬盘中.后续在Redis重启之后,就可以根据刚才生成的快照,把内存中的数据进行恢复处理.
2.1.2 触发机制
我们知道,RDB模式的持久化是定期触发的.关于"定期",具体来说,有两种方式.
- 手动触发
程序员可以通过Redis客户端来执行特定的命令来触发快照的生成.这个命令一共有两种,一种是save命令,一种是bgsave命令.其中save命令执行的时候,Redis就会全力以赴的进行"快照生成"操作,此时就会阻塞Redis的其他客户端的命令,结果类似与keys *
,所以我们一般不建议使用save命令.
之后是bgsave命令,这个命令就不会影响到Redis服务器处理其他客户端的请求和命令.之所以不会影响到其他的Redis客户端,是由于Redis引入了多进程的方式来实现这个功能.其中一个进程负责继续接收客户端的请求,另一个进程负责生成快照并存储到硬盘中.也就是Redis会创建出一个子进程,子进程会完成持久化操作.持久化会把数据写入到新的文件中,然后使用新的文件替换旧的文件.关于文件是否被替换,我们可以使用Linux的stat
命令来查看文件的iNode编号.在执行bgsave命令之后,我们发现文件的iNode有变化.下面是bgsave的执行流程:
下面我们来查看执行bgsave前后两次的iNode.
执行bgsave之后:
我们需要注意的一点就是,save命令是直接在之前的旧文件上写入数据,所以在执行save命令之后,dump.rdb文件的iNode不会改变.
下面我们来对bgsave的实际效果进行展示:
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> bgsave
Background saving started
root@iZ2ze9pwr3i8b65w9dr55dZ:~# service redis-server restart
root@iZ2ze9pwr3i8b65w9dr55dZ:~# redis-cli
127.0.0.1:6379> keys *
1) "key1"
我们发现,在bgsave之后,在我们重新启动服务器的时候,其中的key1没有丢失.
但是快照生成也不是手动输入bgsave命令或者save命令的时候才会触发快照生成,当执行一下操作的时候,也会自动触发触发快照生成:
- 重新启动Redis服务器的时候.
- 下面要讲述的自动生成配置符合相应条件的时候.
- Redis进行主从复制的时候,主结点会自动生成RDB快照,然后把RDB快照通过网络发送给从结点.
比如我们下面不执行快照生成相关命令,直接重新启动Redis服务器.
root@iZ2ze9pwr3i8b65w9dr55dZ:~# redis-cli
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> set key2 value2
OK
127.0.0.1:6379>
root@iZ2ze9pwr3i8b65w9dr55dZ:~# service redis-server restart
root@iZ2ze9pwr3i8b65w9dr55dZ:~# redis-cli
127.0.0.1:6379> keys *
1) "key2"
2) "key1"
我们发现Redis中的数据仍然存在.
如果我们直接使用kill -9
命令直接终止Redis相关进程,Redis就会来不及生成快照,导致数据直接丢失.
root@iZ2ze9pwr3i8b65w9dr55dZ:~# redis-cli
127.0.0.1:6379> set key3 value3
OK
127.0.0.1:6379>
root@iZ2ze9pwr3i8b65w9dr55dZ:~# ps -aux|grep redis
redis 370001 0.1 0.8 67864 14080 ? Ssl 21:24 0:00 /usr/bin/redis-server 0.0.0.0:6379
root 370248 0.0 0.1 6544 2304 pts/0 S+ 21:30 0:00 grep --color=auto redis
root@iZ2ze9pwr3i8b65w9dr55dZ:~# kill -9 370001
root@iZ2ze9pwr3i8b65w9dr55dZ:~# ps -aux|grep redis
redis 370253 0.5 0.8 67864 13824 ? Ssl 21:30 0:00 /usr/bin/redis-server 0.0.0.0:6379
root 370294 0.0 0.1 6544 2304 pts/0 S+ 21:31 0:00 grep --color=auto redis
root@iZ2ze9pwr3i8b65w9dr55dZ:~# redis-cli
127.0.0.1:6379> keys *
1) "key1"
2) "key2"
我们看到key3数据直接丢失了.
- 自动触发
自动触发的其他情况我们在上面讲述过,下面我们只讲述通过配置文件进行定期快照生成.
RDB快照的生成是定期生成,所以我们可以在Redis的配置文件中设置让Redis每隔多长时间就触发一次.其中的配置项是save
配置项.
这些配置项指的是:如果在900秒之内只有1个键值对的修改,那么就是没900秒触发一次.以此类推.当我们把save配置项设置为一个空字符串(“”)的时候,这时候就会关闭快照的定期自动生成.
虽然这些值都可以随便修改,但是我们有一个基本的原则,就是生成一次RDB快照是一个成本比较高的操作,不能让这个操作执行太频繁.
正是由于RDB的快照生成不可以太频繁,这就导致了快照里的数据和当前的实时数据情况可能出现一定的偏差.
比如12点的时候生成了RDB文件,12点01秒开始的时候,redis开始收到了大量的key变化的请求,按照默认配置,RDB至少在12点01分的时候才会生成下一个RDB镜像文件,但是在这期间,Redis服务崩溃,这就导致了12点之后的这些数据全部丢失.
redis生成的RDB文件存放在Redis的工作目录中(默认/var/lib/redis,可以在配置文件中修改).其中有一个dump.rdb文件.这个是Redis的RDB机制生成的镜像文件,Redis服务器就是默认开启了RDB的.这个文件就是RDB保存在硬盘中的持久化文件.
这个文件是一个二进制文件,这个文件就是把内存中的数据以压缩的形式保存到这个二进制文件中的.(使用这种压缩的形式保存,虽然会消耗一定的CPU资源,但是可以节省一定的硬盘空间).
这个文件一般不可以轻易修改,改坏就麻烦了.如果这个文件被改坏了,后续在Redis服务重新启动的时候,Redis会尝试加载这个RDB格式的文件.如果发现这个RDB文件格式错误,或者意外损坏.Redis服务就会启动失败.当然某些情况下还是可以启动成功的,但是这时候Redis中的数据就不是我们期望的数据,就和"开盲盒"一样.
所以Redis提供了RDB文件检查工具,redis-check-rdb <rdb文件路径>
命令可以对rdb是否损坏进行检查.
RDB文件的路径可以在配置文件中做出修改.具体是dir
配置项.文件名也可以在配置文件中做出修改,具体的配置项是dbfilename
.
2.2 AOF模式
上述的RDB机制,有一定优点,也有一定的缺点.
- RDB是⼀个紧凑压缩的⼆进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份,并把RDB文件复制到远程机器或者文件系统中(如hdfs)用于灾备.
- Redis加载 RDB恢复数据远远快于AOF的方式,因为RDB是以二进制的格式保存数据的,恢复的时候直接读取的就是二进制的数据,也就是直接按照字节的方式读取出来的.我们知道Redis存储数据的方式就是直接存储的是二进制数据,没有进行过编码.
- RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork创建子进程,属于重量级操作,频繁执行成本过高
- RDB文件使用特定⼆进制格式保存,Redis版本演进过程中有多个RDB版本,兼容性可能有风险
- RDB最大的缺点还是保存数据的时候是定期保存的,不是实时保存的,这就可能导致生成快照之间,实时的数据可能会随着Redis服务的意外终止而丢失.
为了解决上面RDB的一些缺点,我们引入了AOF机制.
2.2.1 持久化策略
AOF的持久化策略有点类似与MySQL的binlog,把用户的每个操作都记录到文件中,当Redis重新启动的时候,就会读取这个AOF文件内容,用来恢复数据.
其中AOF文件是一个文本文件,每次进行的操作都会被记录到这个文本文件中,通过一些特殊符号作为分隔符,来对命令的细节做出区分.
AOF一般默认是关闭状态,我们需要通过修改配置文件来开启AOF功能.重启Redis服务之后生效.
其中appendfilename
配置用来修改AOF的文件名.
AOF持久化文件的默认路径和RDB持久化文件一样,都是/var/lib/redis
目录下.
AOF机制在备份数据的时候,不是直接让工作线程写入硬盘,而是先写入一个内存中的缓存区,积累一波之后,再统一写入硬盘.这就使得写硬盘的次数大大减少.
其次,AOF每次把新的操作写入到原有文件的末尾,属于顺序写入.而顺序写入的速度是比较快的.正是由于上面的两点,使得AOF虽然既要写内存又要写硬盘,但是他的效率没有降低多少.
但是如果把数据写在缓冲区的时候,本质上还是写在缓存中的,如果注解突然掉电或者进程崩溃,缓冲区的数据就会丢失.这时候,Redis就给出了一些同步硬盘中AOF文件的频率选项,也就是更新缓冲区数据的频率选项.
刷新频率越高,性能影响越大,同时数据的可靠性越高,刷新频率越低,性能影响越小,数据的可靠性越低.
缓冲区刷新率也可以在配置文件中进行配置.具体的配置项是appendfsync
,程序员可以根据自己的需要进行更改.
2.2.2 重写机制
- 概述
随着AOF文件体积的持续增长,体积会越来越大,这就会影响到Redis的下次启动的启动时间.Redis在启动的时候,需要读取AOF文件的内容,我们知道,在AOF中记录的是我们操作Redis的中间过程.实际上,Redis在重新启动的时候,关注的是最终的结果.这时候,AOF就可以把一些冗余的数据进行优化,剔除其中的冗余操作,并且合并一些操作,达到给AOF瘦身的效果,这就是AOF的重写机制.
比如我们有如下的操作:
lpush key 111
lpush key 222
这时候Redis就会合并这些操作,合并为lpush key 111 222.
再比如:
set key 111
set key 222
这时候Redis只会在AOF中保存set key 222这一个操作.
- 流程
AOF重写的机制首先需要创建一个子进程fork.父进程仍然负责接收请求,子进程负责对AOF文件进行重写.
注意: 重写的时候,不关心AOF文件中原来都有什么,只是关心内存中最终的数据状态.子进程只需要把内存中当前的数据获取出来,以AOF文件的格式写入到一个新的AOF文件中.
下面是AOF的重写机制:
此处的子进程写数据的过程,非常类似与RDB生成快照的过程,只不过RDB这里是按照二进制的方式来生成的,而AOF重写则是按照文本格式来生成的.
子进程写新的AOF文件的同时,父进程仍然还在不停地接收客户端的请求.父进程还是会把这些请求产生的AOF数据写入到缓冲区,之后再写入旧的AOF文件中.
- 问题:
- 父进程再fork完毕之后,就已经让子进程写AOF文件了.并且随着时间的推移,子进程很快就会写完新的AOF文件,要让新的AOF文件代替旧的,此时父进程再写这个即将消亡的AOF文件还是否有意义?
有,我们需要考虑到一些极端的情况,比如重写了一半,子进程或者服务器挂掉了,子进程的内存数据就会丢失,新的AOF就会不完整.所以父进程还是会坚持写旧的AOF文件.
- 父进程再fork完毕之后,就已经让子进程写AOF文件了.并且随着时间的推移,子进程很快就会写完新的AOF文件,要让新的AOF文件代替旧的,此时父进程再写这个即将消亡的AOF文件还是否有意义?
在子进程创建的一瞬间,子进程就继承了父进程当前的内存状态,因此,子进程里的内存数据是父进程fork之前的状态,fork之后新来的请求对内存造成的修改是子进程不知道的.此时,父进程这里又准备了一个aof_rewrite_buf缓冲区,专门存放fork之后收到的数据.子进程在把AOF数据全部写完之后,会通过信号通知父进程,父进程再把aof_rewrite_buf缓冲区的内容也写到新的AOF文件中.此时就可以用新的AOF文件代替旧的AOF文件了.
- 问题:
- 如果在执行bgrewirteaof的时候,当前Redis已经在AOF重写,会怎么样?
此时不会在进行AOF重写了,直接返回. - 如果在执行bgrewirteaof的时候,发现当前Redis在生成RDB快照文件,会怎么样?
此时AOF重写操作就会等待,等待RDB快照生成完成之后,再进行AOF重写.
- 如果在执行bgrewirteaof的时候,当前Redis已经在AOF重写,会怎么样?
2.2.3 触发条件
- 手动触发
可以调用bgrewriteaof
指令来进行触发 - 自动触发
可以根据auto-aof-rewrite-min-size
和auto-aof-rewrite-percentage
参数确定自动触发时机.
第一个表示的是当前AOF占用大小相比较上次重写时增加的比例.第二个表示的是触发重写的AOF文件的大小. - 优先级
如果Redis上同时存在AOF文件和RDB快照的时候,此时以AOF文件为主,RDB直接被忽略.
3. 混合持久化
AOF本来是按照文本的方式来写入文件的.但是以文本的方式来写文件,成本是比较高的,于是Redis就引入了混合持久化.结合了RDB和AOF的特点.具体的配置项是aof-use-rdb-preamble
选项.修改配置之后需要重启服务器.
这个配置启动之后,就会按照AOF的妨害,每一个请求/操作都录入文件中,在触发AOF重写之后,就会把当前内存的状态按照RDB的二进制格式写入到新的AOF文件中,后续再进行操作的时候,任然是按照AOF文本的格式追加到文件后面的.