很简单,因为 redis 是基于内存的。数据如果不进行持久化,当服务器重启或者宕机的时候数据是无法恢复的,所以为了保证数据的安全性,我们需要将内存中的数据持久化到磁盘中。
redis 提供了两种持久化的方式,分别是 rdb 和 aof。
- rdb : redis的默认持久化方式,是基于 快照 来实现的,当符合一定条件的时候 redis 会自动将内存中的数据进行快照然后持久化到磁盘中。
- aof : redis默认没有开启 aof 持久化,需要在配置文件中设置 appendonly true 开启。它存储的是 redis 的 顺序指令序列 。
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 1 save 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 的持久化,所以你可能会丢失一段时间的数据。
默认情况下 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 4.0之前,我们一般会开启 aof 然后再需要恢复内存状态的时候弃用 aof日志重放 ( 如果使用rdb的话会丢失大量数据 )。但如果 redis 实例很大,aof 文件也很大的时候会导致 redis 重启非常慢。
为了解决这个问题,在 redis 4.0之后我们可以将 rdb文件和 aof增量日志存储在一起。如果这个时候我们进行内存状态恢复可以先使用前面的 rdb 部分,然后再使用 rdb 持久化之后产生的增量aof日志 来进行内存状态恢复以减少时间。
- 一般来说如果对数据的安全性还是有一定要求的话,应该同时使用两种持久化功能。
- 如果可以承受分钟级别的数据丢失可以仅仅使用 rdb。
- aof 尽量使用 everysec 配置,既能保证数据安全又能保证性能效率。
- rdb 下关闭 save seconds changetimes 这个自动持久化机制,或者合理使用参数。
分享思维导图