redis为什么需要持久化
redis是内存数据库,redis所有的数据都保存在内存中
如果此时pc关机或重启,那么内存中的用户数据岂不是丢失了?redis这么不安全吗?
作为数据库,保证数据的安全,持久是基本需求,redis采用了AOF和RDB两种持久化方式,将用户数据以特殊形式保存在磁盘中,确保重启时可以恢复之前的内存数据状态
AOF(Append Only File)日志
redis的AOF持久化方式是:redis每执行一次写操作,就将对应的命令记录到一个磁盘文件中,redis一旦宕机,重启时重新执行这些命令就可以恢复之前的数据状态
这种保存写操作命令到日志的持久化方式,就是 Redis 里的 AOF(Append Only File) 持久化功能
写日志的步骤:
1.写入aof内核缓冲区
2.将内核缓冲区的数据写入磁盘
Redis 是先执行写操作命令后,才将该命令记录到 AOF 日志里的,这么做其实有两个好处:
1.避免额外的检查,如果一条命令不符合协议的语法,那么这条语句是不会被执行的,如果先写入日志,就会导致恢复数据时,这条命令失效,造成了不必要的性能开销
2.不会阻塞当前写操作的执行,因为执行命令和写日志两个操作都是由主进程进行的,先保证写操作的执行,再进行写日志
AOF持久化的缺陷
1.由于执行写操作和写日志是两个串行的操作,如果在写入日志之前redis故障,就会导致数据丢失
2.写日志操作虽然不会阻塞上一个写操作的执行,但是会阻塞下一个写操作的执行
问题根源——写日志的时机
我们刚刚介绍的,先执行写操作,随后写日志其实是redis日志写策略的一种,现在介绍redis提供的写日志的三种策略
1.Always:每次进行写操作后,将命令写入aof内核缓冲区,随即写入磁盘
2.Everysec:每次进行写操作后,将命令写入aof内核缓冲区,每隔一秒将数据写入磁盘
3.NO:每次进行写操作后将命令写入aof内核缓冲区,由操作系统决定写磁盘的时机
不难发现,每次进行写操作后都必须将命令写入内核aof内核缓冲区,这个过程并不耗时,容易造成阻塞的是写磁盘操作
三种策略的优缺点:
操作系统何时将aof缓冲区的数据写入磁盘?
当应用程序向文件写入数据时,内核通常先将数据复制到内核缓冲区中,然后排入队列,然后由内核决定何时写入硬盘,也就是说,内核缓冲区不止由aof的数据,还有其它的数据,真正的写磁盘时机由操作系统的策略决定
写磁盘时机可以由程序员决定吗?
fsync
函数是操作系统提供的同步函数,可以强制让操作系统立即将内核缓冲区的数据写入到磁盘中
上面提到的 Always和Everysec两种写磁盘策略本质上就是调用了fsync
函数
AOF重写机制
聪明的你,有没有发现AOF日志会记录一些无效信息?
比如,我执行了set name wjq,随后又执行了set name wjq++,此时数据库中name对应的值实际上是wjq++,而wjq这个值被覆盖了,是无效的,而AOF全部记录了这两条命令,并在恢复数据时重新执行这两条命令,而实际上只需要记录第二条命令,这无疑造成了性能浪费
那么,如何判断某条命令(set name wjq)可以不被记录呢?
很简单,只要查看数据库中现有的数据(wjq++),其中的数据是恢复时所必需的
所以,AOF重写机制就是:
读取当前数据库中的所有键值对,然后将每一个键值对用一条命令记录到「新的 AOF 文件」,等到全部记录完后,就将新的 AOF 文件替换掉现有的 AOF 文件。
重写工作完成后,就会将新的 AOF 文件覆盖现有的 AOF 文件,这就相当于压缩了 AOF 文件,使得 AOF 文件体积变小了。
AOF后台重写
之所以使用AOF重写机制,不是因为AOF记录了无效的信息,而是因为当数据量过大时,AOF文件占用空间过多,我们需要将AOF文件进行压缩
所以,AOF重写机制的触发时机是:当 AOF 文件大于 64M 时
触发 AOF 重写时,比如当 AOF 文件大于 64M 时,就会对 AOF 文件进行重写,这时是需要读取所有缓存的键值对数据,并为每个键值对生成一条命令,然后将其写入到新的 AOF 文件,重写完后,就把现在的 AOF 文件替换掉
你有没有发现,在正常的AOF日志写操作时,是对每一条命令写入aof缓冲区,这个过程并不耗时
但AOF重写时,AOF文件已经超过了64M,这时进行生成命令、写入新文件的操作相当耗时,万万不能放在主进程中进行,那怎么办呢?
所以,Redis 的重写 AOF 过程是由后台子进程 bgrewriteaof 来完成的
当需要进行AOF重写时,fork出一个子进程,由子进程执行重写操作,这样就不会阻塞主进程了
重写期间数据不一致的问题
考虑这样的情况:在子进程进行AOF重写操作时,主进程添加或修改了新的数据,这时子进程只拥有fork时刻的父进程的副本,而没有新添加的数据,并且在AOF重写完成后会用新AOF文件覆盖旧的AOF文件,也就是说,这种数据不一致会导致AOF重写期间添加和修改的数据丢失
解决:
既然在AOF重写期间的新数据只会出现在父进程,而不会出现在子进程,那我们将新数据追加到子进程不就好了
方案:
父进程拥有aof缓冲区
子进程拥有aof重写缓冲区
1.先执行客户端发送的写操作命令
2.将命令写入父进程aof缓冲区
3.将命令写入子进程aof重写缓冲区
这样就解决了父子进程数据不一致和数据丢失的问题
总结
1.AOF日志的持久化具体过程:每执行一条写命令,将该命令写入aof缓冲区,然后由三种写策略(always、everysec、no)决定何时将aof缓冲区数据写入磁盘文件
2.在数据库恢复时,重放磁盘中AOF文件中保存的命令,就实现了内存数据的恢复
3.由于aof记录每一条写命令,会造成记录无效数据的情况,并且数据量大时,aof文件的体积也会过大
为缓解aof的空间占用,redis设计了aof重写的机制,它会扫描数据库中的键值对,并为之生成命令,写入一个新的aof文件中,全部键值对写入完成后,新的aof文件会覆盖旧aof文件,这个过程避免了无效命令的记录,压缩了aof文件体积
大时,aof文件的体积也会过大
为缓解aof的空间占用,redis设计了aof重写的机制,它会扫描数据库中的键值对,并为之生成命令,写入一个新的aof文件中,全部键值对写入完成后,新的aof文件会覆盖旧aof文件,这个过程避免了无效命令的记录,压缩了aof文件体积
不过由于执行aof重写操作的时机是旧aof文件超过64M,也就是说aof重写操作将会很耗时,redis使用一个fork出的子进程来执行这个任务,但是在子进程进行aof重写时,如果父进程产生了新aof数据,会造成数据不一致,且在子进程完成aof重写并覆盖旧aof文件后,这些多出的不一致数据会丢失,所以在aof重写期间追加的新aof数据会先写入父进程aof缓冲区,再写入子进程aof重写缓冲区,解决了数据不一致和数据丢失的问题