为什么需要持久化
很简单,因为 Redis 是基于内存的。数据如果不进行持久化,当服务器重启或者宕机的时候数据是无法恢复的,所以为了保证数据的安全性,我们需要将内存中的数据持久化到磁盘中。
Redis的持久化
Redis 提供了两种持久化的方式,分别是 RDB 和 AOF。
- RDB : Redis的默认持久化方式,是基于 快照 来实现的,当符合一定条件的时候 Redis 会自动将内存中的数据进行快照然后持久化到磁盘中。
- AOF : Redis默认没有开启 AOF 持久化,需要在配置文件中设置 appendonly true 开启。它存储的是 Redis 的 顺序指令序列 。
RDB方式的持久化
save 阻塞方式
在 Redis 中有一个命令可以触发 RDB 持久化,但是这个操作会阻塞 Redis。
这个命令是 save,我们都知道 Redis 是单线程的,如果持久化进行 特殊的处理 的话,那么就会阻塞其他命令导致 Redis 短时间内不可用,如果 RDB 文件很大,那么刷盘操作将会数十秒,严重影响可用性,所以我们一般都不会使用 save 命令。
bgsave 后台方式
bgsave 顾名思义,就是 后台进行保存 。当执行这条命令的时候 Redis 就会进行一些 特殊处理 。
什么特殊处理呢?
首先 Redis 的主进程会调用 glibc 的函数 fork 产生一个子进程,此时会将文件 持久化全部交给子进程去处理,那么这时父进程就可以继续处理用户的请求了(bgsave执行之后直接会返回)。当然,在主进程进行 fork 操作的时候可能也会对用户请求命令 产生短暂的阻塞 。
凡事有利必有弊,你看 bgsave 这么好,难道没有缺点么? 答案是有的,在哪呢?我们先来了解一下 COW 机制吧。
COW
COW (copy on write),也就是 写时复制 。我们知道 RDB 持久化需要遍历内存中的数据,就像下面那张图一样。
因为我们需要的是在子进程产生的那一瞬间的数据(快照),如果此时因为用户请求在主进程修改了内存中的数据,那么子进程遍历的内存就会被更改,这个时候就不是快照数据了。
所以这里就使用了产生快照的一种机制—— COW 。我们知道上面的数据段其实由很多操作系统的页组合而成的。COW 其实就是在主进程需要修改内存中的数据的时候,首先将需要修改的数据所在的页进行复制,然后再复制的页面上进行修改。当进行页的复制的时候就会占用额外的内存,这也是 bgsave 占用内存比 save 多的原因,但是不用过分担心,因为再保存期间不会出现大量的用户请求来修改数据,额外使用的内存也不会很多。
自动触发RDB
在 Redis 中会有几种情况下进行 RDB 的持久化,所以即使你在配置文件中关闭了 RDB ,Redis 还是会进行 RDB 的持久化。
- 满足条件时 在 Redis 的配置文件中有这么三条配置。
save 900 1save 300 10 save 60 10000
这其实就是 RDB 中默认开启的原因,它的格式是这样的 save seconds changeTimes,save 后面的第一个数字是时间,第二个数字是修改次数,这三条配置的意思就是 在900秒内进行了1次修改或者在300秒内进行了10次修改或者在60秒内进行了10000次修改 会进行 RDB 的自动持久化。
- shutdown 当 Redis 正常关闭的时候会进行 RDB 持久化。
- flushall 会产生一个空的 RDB 文件
- 主从复制进行全量复制的时候(目前做了解就行,我在后面的文章会讲到 Redis 集群)
RDB的优点
- RDB 文件,其中做了些压缩,存储的是数据,可以快速的进行灾难恢复。
- 适合做冷备。
RDB的缺点
- 容易丢失数据,因为 RDB 需要遍历整个内存中的所有数据,所以进行一次 RDB 操作是一个费事费力的操作,为了保证 Redis 的高性能,你需要尽量减少 RDB 的持久化,所以你可能会丢失一段时间的数据。
AOF方式的持久化
默认情况下 Redis 是没有开启 AOF 持久化的,你需要在配置文件中进行相应的配置。
appendonly yes # 默认为no这里设置yes开启dir ./ # aof文件目录appendfilename "appendonly-6379.aof" #aof文件名
开启 AOF 持久化之后,Redis 会根据 AOF 持久化策略 来进行相应的持久化操作,具体配置是在 appendfsync 配置。
# appendfsync always 每次进行修改操作就进行写盘appendfsync everysec 每秒进行一些写盘# appendfsync no 从不
Redis一共提供了三个参数,一般考虑设置 everysec 每秒写盘,这样既能少影响效率也能减少数据丢失量。
AOF原理
AOF 日志中存放的是对于 Redis 的操作指令,有了 AOF 日志我们就可以使用它进行对 Redis 的重放。
比如此时我们 AOF 日志中记录了 set hello world 和 sadd userset FancisQ 这两条命令,我们就可以对一个空的 Redis 实例进行此 AOF 文件的重放,最终这个空的 Redis 就有了上面两条记录。
你可能会发现 Redis 中的这两个持久化方式很像 MySQL 中的 bin log 和 redo log,但是你需要注意的是 Redis 中的 AOF 是先执行命令再存日志的。这和 MySQL 中的 WAL 机制截然相反。
为什么呢? 我觉得有两点。
- Redis 是弱事务的,我们不需要保证数据的强一致。在 MySQL 中我们使用了 redo log 两阶段提交 来保证了 save-crash 能力,而在 Redis 中我们显然不需要这么做,假设这条命令执行完之后还没来得及写日志就宕机了,那就没了,因为弱事务,我们大可不必保证数据必须存在。
- 为了避免错误指令的日志存储,如果先写日志也就意味着我们一开始没有做相应的 逻辑处理和参数校验 ,所以这样会 先记录到很多错误指令 ,但是我们知道 AOF 文件是需要 瘦身 的,这些错误指令会给 AOF 瘦身带来很多麻烦。
AOF重写
上面提到的 瘦身 其实就是 AOF重写 ,我们知道 AOF 文件中存储的是指令顺序,当 Redis 长时间运行时会产生很多指令。
比如 set a b,set a c,set a d.....
其实上面三条就是对 key 为 a 的数据进行操作了,在 RDB 中它可能只存了 a = d ,但是因为 AOF 的指令机制,它必须存在三条,但前面的是无意义的,这样会浪费很多空间并且给 AOF 重放带来麻烦。
所以 Redis 会在 AOF文件过大(符合某种条件)的时候进行自动的 AOF 重写。对应的在配置文件中有这样两条配置。
# 下面两条需要同时满足# 表示当前 aof 文件超过上次 aof文件大小的百分之多少时会进行重写,如果没有重写过则以启动时的大小为标准auto-aof-rewrite-percentage 100 # 文件大于多少的时候进行重写auto-aof-rewrite-min-size 64mb
那么,AOF 是如何重写的呢?
bgrewriteaof
这是一条 AOF 重写命令(上述的重写过程其实就是 bgrewriteaof),和 bgsave 一样,Redis 也是会 fork 一个子进程,让子进程去负责 AOF 文件的重写。大致流程如下:
AOF的优缺点
- 缺点: 在同等数据量的情况下,AOF 文件的大小要比 RDB 文件大得多,如果使用它进行内存状态恢复需要花费很长时间。
- 优点: 持久化快,能减少数据丢失的量,在配置 everysec 的情况下最多只会丢失秒级的数据。
Redis混合持久化
在 Redis 4.0之前,我们一般会开启 AOF 然后再需要恢复内存状态的时候弃用 AOF日志重放 ( 如果使用RDB的话会丢失大量数据 )。但如果 Redis 实例很大,AOF 文件也很大的时候会导致 Redis 重启非常慢。
为了解决这个问题,在 Redis 4.0之后我们可以将 RDB文件和 AOF增量日志存储在一起。如果这个时候我们进行内存状态恢复可以先使用前面的 RDB 部分,然后再使用 RDB 持久化之后产生的增量AOF日志 来进行内存状态恢复以减少时间。
如何选择 RDB 和 AOF
- 一般来说如果对数据的安全性还是有一定要求的话,应该同时使用两种持久化功能。
- 如果可以承受分钟级别的数据丢失可以仅仅使用 RDB。
- AOF 尽量使用 everysec 配置,既能保证数据安全又能保证性能效率。
- RDB 下关闭 save seconds changeTimes 这个自动持久化机制,或者合理使用参数。
分享思维导图
最后
如果本文章对你看完之后有所帮助,帮忙关注转发一下,谢谢
喜欢更多的技术文,面试文、可以关注我的公众号:java小瓜哥的分享平台,我平时整理的文章和资料全放在里面了。谢谢!