文章目录
持久化
MySQL的事务有四个特性:原子性,一致性,持久性和隔离性。
这里的持久性和Redis中的持久化,是一样的。
为什么Redis能够火起来呢?原因还是因为效率高,快!
为什么快呢?对于MySQL这种关系型数据库来说,Redis作为一个内存数据库。
数据都是放在内存上的,所以才快。
但是!
放在内存上,是不持久的,一关机重启,就没了,为了解决这个问题,就引入了持久化的特性。
Redis最后采取的策略:内存也存,硬盘也存数据。
当要插入一个新数据时,在内存写入一份,在硬盘也写入一份。
但是当查询某个数据时,只在内存中读取,硬盘的数据只是在Redis重启后,用来恢复内存中的数据的。
Redis实现持久化的两种策略
Redis实现持久化的两种策略:RDB和AOF策略。
两种策略的区别是:
RDB:相当于定期备份。
AOF:相当于实时备份。
RDB策略:Redis DataBase
RDB 持久化是把当前进程数据⽣成快照保存到硬盘的过程,触发 RDB 持久化过程分为⼿动触发和 ⾃动触发。
RDB就是定期把Redis所有的内存数据,都写入硬盘中,生成一个”快照“。
什么是 ”快照“?
举个例子:在某个案发现场,警察来了,马上拉上警戒线,拍照留档记录现场,后续就能根据这些记录,还原现场发生了什么,这个就是快照
Redis给内存的所有数据都拍个照记录,生成一个文件,保存在硬盘中,这个文件就是所谓的**“快照”**
后续重启电脑后,就能根据“快照”,还原数据。
RDB定期快照的两种方式(手动触发和自动触发)
- 1.手动触发
通过Redis客户端,执行特定命令来触发快照生成。
save,bgsave命令
save命令:执行该命令时,redis就会全力以赴地生成“快照”,而阻塞redis其他客户端的命令。就可能导致类似 keys 星这种阻塞后果。(一般不用这个命令,因为风险大)
bgsave命令:background save:在后台保存(像linux的前台命令和后台命令)。不会影响redis服务器处理其他客户端命令的操作。
怎么做到呢? Redis使用的是“多进程”的方式来完成bgsave的实现。
bgsave多进程的实现快照保存的流程如下:
1)Redis服务器判断当前是否已经有其他正在工作的子进程(执行bgsave的),如果有,则直接返回,因为生成快照只需要一份就够了。
2)如果没有其他工作的子进程,就直接调用fork创建子进程来快马加鞭地保存快照(生成.rdb文件,父进程则正常处理其他客户端的请求。
3)子进程处理完持久化任务后,通过信号通知父进程,父进程就会统计更新信息,干完之后,就可以销毁子进程了。
这里有问题了:
如果Redis服务器的内存使用非常大,足足有100个G,此时Redis调用fork创建子进程的时候,会不会出现非常大的拷贝开销呢?
要知道,fork创建子进程或完全复制一份父进程的pcb结构对象,页表,虚拟地址和物理地址的映射关系,文件描述符表等信息。
实际上,性能上开销是非常小的,因为用到了一个写时拷贝的技术。
举个简单例子:
父子进程在没有对数据修改时,是直接使用同一份内存数据的。
只有当父进程/子进程对数据修改时,这时候才会执行真正意义上的拷贝。
所以就算此时父进程(redis服务器)使用100G内存,如果修改的地方很小,性能的开销也是很小的。
不用担心。
关于RDB文件:
当执行生成RDB镜像文件的操作时,此时会把要生成的快照数据,先保存到一个临时文件中。
然后当快照数据全部保存到临时文件后,此时才删除原来的dump.rdb文件,然后再把这个临时文件改名为dump.rdb——这样就保证了rdb文件始终只有一个。
(这样做也是为了防止极端情况下:在向.rdb文件写入二进制数据过程redis服务器挂了,此时数据就可能被覆盖,就会消失)
- 2.自动触发
1) 在redis的配置文件中,设置让Redis每隔多长时间/每隔多少次修改就触发快照。
2)如果是正常退出redis服务器,(比如shutdown命令,或者service redis-server restart,就会自动触发一次快照保存)
但如果是异常情况,比如服务器掉电了,或者是kill -9终止,redis就来不及进行快照保存,就会出现问题。
3)主从复制,主节点会将当前数据写入.rdb文件保存,完成后再将这个文件交给从节点(后面细谈)
题外话:可以再redis客户端上演示来查看进行bgsave时,出现的临时文件替换原有的.rdb文件的过程的。
原理如下:通过Linux的stat命令查看相同的文件内容的不同的文件inode就可观察到了。
如果故意把.rdb文件故意损坏会怎么样?
RDB定期生成快照无法解决的一个问题:
假如我在配置文件设置了,60s内,key修改10000次,就会触发快照。
在12:00时候,生成了一次快照,然后此时有大量的key的变化请求到来,但是突然Redis服务器挂了,此时就会丢失12:00后的大量数据!
为了解决这个问题,就引入了AOF实时快照保存策略。
RDB策略总结
1)RDB是一个紧凑压缩的二进制文件,代表Redis在某个时刻上的数据快照。非常适合备份,全量复制的场景。
2)Redis加载RDB恢复数据远快与AOF方式,因为RDB使用的是二进制的方式来存储数据,直接把数据读取到内存中,按照字节的格式读取出来,放到结构体中。而AOF是以文本的方式来组织数据的,需要进行一系列的字符串切分操作。
3)RDB方式的数据没法做到实时持久化,秒级持久化,因为bgsave每次运行都要执行fork,属于重量级操作,频繁执行的话,成本过高。
4)RDB文件使用特定二进制格式保存,老版本的redis的rdb文件在新版本可能用不了。
(解决办法:通过写一个程序的方式,直接遍历旧的redis中的所有的key,把数据取出来,插入到新的redis服务器即可。)
RDB最大的问题是在两次快照之间,实时的数据可能随着重启而丢失(异常重启)
AOF策略:Append Only File
AOF持久化流程图:
AOF将数据保存的具体操作是:记录用户的每条操作,即记录用户的命令
记录了命令,则具体的内容也就随之记录了。
那问题来了,当redis重启的时候,读取的是.rdb文件呢,还是读取.aof文件?
读取的是.aof文件。当AOF开启时,就不再读取.rdb文件了
aof默认是关闭的,可以在配置文件中修改。
AOF是一个文本文件,用户每次进行命令操作,都会记录该命令到.aof文本文件中。
所以在异常重启时,通过读取.aof文件,就能还原数据了。(实时性)
问题:引入AOF机制和RDB机制来保证持久化后,redis的效率还有那么高嘛?(毕竟redis是一个单线程的服务器)
1)实际上是没有影响的,AOF机制不是直接让工作线程b把数据写入到硬盘,而是先写入内存中的缓冲区,积累一波后,再一次写入硬盘,这样就大大减少了写入硬盘的次数。
2)AOF是每次用户执行一个操作后,把这个操作写入到原.aof文件的末尾,属于顺序写入。而硬盘读取数据,顺序读取的速度还是比较快的(当然比内存读取慢的多),随机读取比较慢。所以基于上面两点,没啥影响。
AOF缓冲区刷新策略
上面提到了把操作的命令写入到了内存中的缓冲区中,积累一波再写入到硬盘,万一再写入缓冲区的过程中,redis服务器异常终止后,咋办?缓冲区的数据不是就丢了嘛?
是的,就是丢了。
这个问题,redis提供了几种选择给用户。
一般来说,刷新频率越高,数据可靠性越高,但是性能越低(要么牺牲性能保证数据可靠,要么牺牲数据可靠性保证性能),反之刷新频率越低,数据可靠性越低,但是性能越高。
always:每次把命令写入缓冲区,则立即写入.aof文件
everysec:每隔1秒把缓冲区的用户命令写入.aof文件。
no:用户命令写入内存缓冲区后,不会主动写入.aof文件,需要主动操作。
从上面看,当持续不断地向.aof文件写入用户命令,.aof文件会持续增大,那会不会大到一定程度,就出问题呢?
答案是有可能的,因为下次redi服务器启动,需要读取.aof文件的命令来加载数据到内存。aof文件体积大了,redis服务器启动速度就慢。
但是要注意一个问题:aof文件中的许多数据是冗余的!!
此时aof文件存储的命令有:
lpush key 111
lpush key 222
这两个命令可以合并成一个:lpush key 111 222
set key 111
set key 222
set key333 …
这三个命令可以合并成一个:set key 333
aof文件中大量存在上述冗余的命令,所以就必须设置一个机制,来清楚这些冗余的命令,给aof文件达到一个瘦身的效果。
所以要解决这个问题:用AOF重写机制。
AOF重写机制
AOF重写机制手动触发:bgrewriteaof命令:bg_re_write_aof:重新在后台重写aof文件
AOF重写机制自动触发:
在配置文件中:
根据 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 参数确定⾃动触发时机。
◦ auto-aof-rewrite-min-size:表示触发重写时 AOF 的最⼩⽂件⼤⼩,默认为 64MB。
◦ auto-aof-rewrite-percentage:代表当前 AOF 占用大小相比较上次重写时增加的比例。
AOF重写流程:
(绿色字部分相当于多留条出路了)
下一次再进行AOF重写时,fork创建子进程后子进程又会重新写时拷贝了父进程的aof内存缓冲区,又执行上面那套流程了。
一些小问题:
问题:如果在执行bgrewriteaof的时候,当前redis已经在进行aof重写了,会怎样呢?
此时不会再执行aof重写了,直接返回。
问题:如果在执行bgrewriteaof的时候,当前redis已经在进行rdb快照的生成,会怎样呢?
此时aof重写就会等待rdb快照生成完毕后,再进行执行aof的重写。
混合持久化
结合两种持久化策略。
RDB持久化策略的优点是数据内容紧凑,用.rdb文件恢复较快。
但是缺点也很明显,调用fork生成子进程属于大工程,不能频繁fork,所以无法实现实时持久化。
AOF持久化策略的优点是:可以做到每次都把命令写入到磁盘的AOF文件中,而无需调用fork。就能实现实时持久化。
但是缺点就是:这样做容易导致命令冗余和AOF文件较大,加载时效率较低。为了解决这个问题引入了AOF重写策略,但是即便这样体积也还是稍微大了点。
基于以上两个策略的优缺点实现了混合持久化:
混合持久化流程:
1.第一次触发快照时,将内存缓冲区中的数据(不是命令)保存到AOF文件中。
在保存快照期间,用户的命令(不是数据)会追加到AOF文件的末尾。
2.当AOF文件大到阈值时,触发AOF重写机制。
1)fork子进程,此时子进程保存了父进程的内存缓冲区的数据(不是命令),并将数据以RDB格式写入到新的AOF文件中。
2)在子进程重写期间,父进程会继续同时向旧的AOF缓冲区和新的AOF缓冲区中写入用户命令数据,用户的命令数据会在RDB数据写入到AOF文件后,以AOF的格式写入到新的AOF文件的末尾(前面部分有第一步写的RDB二进制数据)
3)此时父进程也会同样向旧的AOF文件中继续写入重写期间用户的命令(这个就是防止极端情况下重写期间服务器挂掉了)
4)重写完成,将新的AOF文件覆盖旧的AOF文件(AOF文件前部分是rdb格式的数据,后部分是aof格式的命令数据)
结果:Redis重启时,检测到AOF文件前面是rdb数据,加载速度快。然后后续加载AOF命令,恢复到最新状态。
优点:既保证了Redis服务器重启时的加载速度(读取rdb数据快),又保证了纯RDB策略下,解决 RDB 两次快照间数据丢失的问题。
两种持久化策略的总结
- Redis 提供了两种持久化方案:RDB 和 AOF。
- RDB 视为内存的快照,产⽣的内容更为紧凑,占⽤空间较小,恢复时速度更快。但产⽣ RDB 的开销较大,不适合进⾏实时持久化,⼀般⽤于冷备和主从复制。
- AOF 视为对修改命令保存,在恢复时需要重放命令。并且有重写机制来定期压缩 AOF ⽂件。
- RDB 和 AOF 都使⽤ fork 创建⼦进程,利⽤ Linux 子进程拥有父进程内存快照的特点进行持久化,尽可能不影响主进程继续处理后续命令的处理。