网上虽然已经有不少相似的介绍了,但我仍是本身总结概括了一下,自认为内容和细节都是比较齐全的。html
文章篇幅有 4k 多字,货有点干,断断续续写了好几天,但愿对你们有帮助。不出意外地话,从此会陆续更新 Redis 相关的文章,和你们一块儿学习。redis
好了,下面开始回归正文:数据库
Redis 一共有 2 种持久化方式,分别是 RDB 和 AOF,下面我来详细介绍两种方式在各个过程所作的事情,特色等等。编程
RDB 持久化是 Redis 默认的持久化方式。设计模式
它所生成的 RDB 文件是一个压缩的二进制文件,经过该文件能够还原生成 RDB 文件时的数据库状态缓存
PS:数据库状态是指 Redis 服务器的非空数据库以及他们键值对的统称安全
有两个命令能够生成 RDB 文件,一个是 SAVE、另外一个是 BGSAVE。服务器
二者的区别在于:前者会阻塞 Redis 服务器进程,直到 RDB 文件建立完毕为止。 微信
而在服务器进程阻塞期间,服务器是不能处理任何命令请求的。数据结构
后者则不会阻塞服务器进程,由于是经过 fork 一个子进程,并让其去建立 RDB 文件,而服务器进程(父进程)继续则继续处理命令请求。
当写完数据库状态后,新 RDB 文件就会原子地替换旧的 RDB 文件。
此处小提问:若是在执行 BGSAVE 期间,客户端发送 SAVE、BGSAVE 或 BGREWRITEAOF 命令给服务端,服务端会如何处理呢?答案:在执行 BGSAVE 期间,上述三个命令都不会被执行。
详细缘由:前两个会被直接拒绝,缘由是为了不父子进程同时执行两个 rdbSave 调用,防止产生竞争条件。
而 BGREWRITEAOF 命令则是会被延迟到 BGSAVE 命令执行以后再执行。
但若是是 BGREWRITEAOF 命令正在执行,此时客户端发送 BGSAVE 命令则会被拒绝。
由于 BGREWRITEAOF 和 BGSAVE 都是由子进程执行的,因此在操做方面没有冲突的地方,不能同时执行的缘由是性能上的考虑——并发出两个子进程,而且这两个子进程都会同时执行大量 io(磁盘写入)操做
RDB 文件的载入是在服务器启动时自动执行的,因此没有用于载入的命令,期间阻塞主进程。
只要没有开启 AOF 持久化功能,在启动时检测到有 RDB 文件,就会自动载入。
当服务器有开启 AOF 持久化功能时,服务器将会优先使用 AOF 文件来还原数据库状态。缘由是 AOF 文件的更新频率一般比 RDB 文件的更新频率高。
对于 RDB 持久化而言,咱们通常都会使用 BGSAVE 来持久化,毕竟它不会阻塞服务器进程。
在 Redis 的配置文件,有提供设置服务器每隔多久时间来执行 BGSAVE 命令。
Redis 默认是以下配置:
save 900 1 // 900 秒内,对数据库至少修改 1 次。下面同理
save 300 10
save 60 10000
只要知足其中一种状况,服务器就会执行 BGSAVE 命令。
咱们从上面的介绍知道,RDB 持久化经过保存数据库状态来持久化。而 AOF 与之不一样,它是经过保存对数据库的写命令来记录数据库状态。
好比执行了 set key 123
,Redis 就会将这条写命令保存到 AOF 文件中。
在服务器下次启动时,就能够经过载入和执行 AOF 文件中保存的命令,来还原服务器关闭前的数据库状态了。
整体流程和 RDB 持久化同样 —— 都是建立一个 xxx 文件、在服务器下次启动时就载入这个文件来还原数据
那么,AOF 持久化具体是怎么实现的呢?
AOF 持久化功能的实现能够分为 3 个步骤:命令追加、文件写入、文件同步
其中命令追加很好理解,就是将写命令追加到 AOF 缓冲区的末尾。
那文件写入和文件同步怎么理解呢?刚开始我也一脸懵逼,终于在网上找到了答案,参考见文末,有兴趣的读者能够去看看。
先不卖关子了,简单一句话解释就是:前者是缓冲区内容写到 AOF 文件,后者是将 AOF 文件保存到磁盘。
ok,明白什么意思以后,咱们稍微详细看下这两个东西是什么鬼。
在《Redis设计与实现》中提到,Redis 服务器进程就是一个事件循环,这个循环中的文件事件(socket 的可读可写事件)负责接收客户端的命令请求,以及向客户端发送命令结果。
由于服务器在处理文件事件时,可能会发生写操做,使得一些内容会被追加到 AOF 缓冲区末尾。因此,在服务器每次结束一个事件循环以前 ,都会调用 flushAppendOnlyFile 方法。
这个方法执行如下两个工做:
两个步骤都须要根据必定的条件来执行,而这些条件由 Redis 配置文件中的 appendfsync 选项来决定的,一共有三个选择:
下面说下三个的区别:
appendfsync no:每次执行完一个命令以后, WRITE 会执行,SAVE 都会被忽略,只会在如下任意一种状况中被执行:
而对于操做特性来分析的话,则是以下状况:
模式 | WRITE 是否阻塞主进程 | SAVE 是否阻塞主进程 | 停机时丢失的数据量 |
---|---|---|---|
appendfsync always | 阻塞 | 阻塞 | 最多只丢失一个命令的数据 |
appendfsync everysec | 阻塞 | 不阻塞 | 通常状况下不超过 2 秒钟的数据 |
appendfsync no | 阻塞 | 阻塞 | 操做系统最后一次对 AOF 文件触发 SAVE 操做以后的数据 |
既然 AOF 持久化是经过保存写命令到文件的,那随着时间的推移,这个 AOF 文件记录的内容就愈来愈多,文件体积也就愈来愈大,对其进行数据还原的时间也就愈来愈久。
针对这个问题,Redis 提供了 AOF 文件重写功能。
经过该功能来建立一个新的 AOF 文件来代替旧文件。而且两个文件所保存的数据库状态同样,但新文件不会包含任何冗余命令,因此新文件要比旧文件小得多。
而为何新文件不会包含任何冗余命令呢?
那是由于这个重写功能是经过读取服务器当前的数据库状态来实现的。虽然叫作「重写」,但实际上并无对旧文件进行任何读取修改。
好比旧文件保存了对某个 key 有 4 个 set 命令,通过重写以后,新文件只会记录最后一次对该 key 的 set 命令。所以说新文件不会包含任何冗余命令
由于重写涉及到大量 IO 操做,因此 Redis 是用子进程来实现这个功能的,不然将会阻塞主进程。该子进程拥有父进程的数据副本,能够避免在使用锁的状况下,保证数据的安全性。
那么这里又会存在一个问题,子进程在重写过程当中,服务器还在继续处理命令请求,新命令可能会对数据库进行修改,这会致使当前数据库状态和重写后的 AOF 文件,所保存的数据库状态不一致。
为了解决这个问题,Redis 设置了一个 AOF 重写缓冲区。在子进程执行 AOF 重写期间,主进程须要执行如下三个步骤:
当子进程结束重写后,会向主进程发送一个信号,主进程接收到以后会调用信号处理函数执行如下步骤:
当函数执行完成后,主进程就继续处理客户端命令。
所以,在整个 AOF 重写过程当中,只有在执行信号处理函数时才会阻塞主进程,其余时候都不会阻塞。
到目前为止,Redis 的两种持久化方式就介绍得差很少了。可能你会有疑惑,在实际项目中,我到底要选择哪一种持久化方案呢?下面,我贴下官方建议:
一般,若是你要想提供很高的数据保障性,那么建议你同时使用两种持久化方式。若是你能够接受灾难带来的几分钟的数据丢失,那么你能够仅使用 RDB。
不少用户仅使用了 AOF,可是咱们建议,既然 RDB 能够时不时的给数据作个完整的快照,而且提供更快的重启,因此最好仍是也使用 RDB。
在数据恢复方面:
RDB 的启动时间会更短,缘由有两个:
注意:
上面说了 RDB 快照的持久化,须要注意:在进行快照的时候(save),fork 出来进行 dump 操做的子进程会占用与父进程同样的内存,真正的 copy-on-write,对性能的影响和内存的耗用都是比较大的。好比机器 8G 内存,Redis 已经使用了 6G 内存,这时 save 的话会再生成 6G,变成 12G,大于系统的 8G。这时候会发生交换;要是虚拟内存不够则会崩溃,致使数据丢失。因此在用 redis 的时候必定对系统内存作好容量规划。
目前,一般的设计思路是利用复制(Replication)机制来弥补 aof、snapshot 性能上的不足,达到了数据可持久化。即 Master 上 Snapshot 和 AOF 都不作,来保证 Master 的读写性能,而 Slave 上则同时开启 Snapshot 和 AOF 来进行持久化,保证数据的安全性。
文章知识点有点多和杂,我总结一下,帮助他们回顾内容:
参考:
《redis设计与实现》
https://www.cnblogs.com/zhouj...
https://redisbook.readthedocs...
PS:本文原创发布于微信公众号「不仅Java」,坚持原创!后台回复如下关键字获取经典必读书籍:
Java、MySQL、Redis、Linux、mq、数据结构、设计模式、编程思想、架构