快速理解Redis的持久化

Redis系列文章

为什么需要持久化

很简单,因为 Redis基于内存的。数据如果不进行持久化,当服务器重启或者宕机的时候数据是无法恢复的,所以为了保证数据的安全性,我们需要将内存中的数据持久化到磁盘中。

Redis的持久化

Redis 提供了两种持久化的方式,分别是 RDBAOF

  • 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 的持久化,所以即使你在配置文件中关闭了 RDBRedis 还是会进行 RDB 的持久化。

  • 满足条件时

    Redis 的配置文件中有这么三条配置。

    save 900 1
    save 300 10
    save 60 10000
    复制代码

    这其实就是 RDB 中默认开启的原因,它的格式是这样的 save seconds changeTimessave 后面的第一个数字是时间,第二个数字是修改次数,这三条配置的意思就是 在900秒内进行了1次修改或者在300秒内进行了10次修改或者在60秒内进行了10000次修改 会进行 RDB 的自动持久化。

  • shutdownRedis 正常关闭的时候会进行 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 worldsadd userset FancisQ 这两条命令,我们就可以对一个空的 Redis 实例进行此 AOF 文件的重放,最终这个空的 Redis 就有了上面两条记录。

你可能会发现 Redis 中的这两个持久化方式很像 MySQL 中的 bin logredo log,但是你需要注意的是 Redis 中的 AOF先执行命令再存日志的。这和 MySQL 中的 WAL 机制截然相反。

为什么呢? 我觉得有两点。

  1. Redis 是弱事务的,我们不需要保证数据的强一致。在 MySQL 中我们使用了 redo log 两阶段提交 来保证了 save-crash 能力,而在 Redis 中我们显然不需要这么做,假设这条命令执行完之后还没来得及写日志就宕机了,那就没了,因为弱事务,我们大可不必保证数据必须存在。
  2. 为了避免错误指令的日志存储,如果先写日志也就意味着我们一开始没有做相应的 逻辑处理和参数校验 ,所以这样会 先记录到很多错误指令 ,但是我们知道 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 这个自动持久化机制,或者合理使用参数。

思维图谱

感谢阅读。 给你们分享一下思维导图 (#^.^#)

猜你喜欢

转载自juejin.im/post/5dca0b8af265da4d245910f5