redis 持久化

前言: 什么是持久化

众所周知, redis 是一个内存数据库, 会把数据存入内存中, 但是内存中的数据是不持久的, 当服务器重启之后, redis 上次存入内存的数据就消失了. 所以为了保证应用程序的可用性, 就需要让 redis 服务器中的数据持久化保存.

所谓的持久化, 就是把数据存入硬盘中, 这样下来, 即使 redis 服务器重启了, 也可以从硬盘中读取数据, 保证数据的持久性.

redis 采取的持久化策略

redis 是一个内存数据库, 其最主要的特点就是: ! 而快的原因就是把数据存入内存中而不是存在硬盘中, 因为读取内存的速度比读取硬盘上的速度快了好几个数量级.
为了快这个初心, 即使 redis 需要将数据持久化而存入硬盘中, 也不会放弃将数据存入内存.

所以, redis 所采取的持久化的策略是: 在内存和硬盘中都存入数据. 而在硬盘和内存中的数据, “理论” 上是相同的.

持久化的思路:

  • 当要插入一个数据的时候, 就需要把这个数据在内存和硬盘中各写入一份
  • 当要查询数据的时候, 直接去内存中读取就行
  • 当 redis 服务器重启的时候, 用硬盘中的数据来恢复内存中的数据

在上述思路中, redis 官方提供了两种持久化的策略:
1. RDB (Redis DataBase)

2. AOF (Append Only File)

对于上述两个持久化策略, 用一句话来概括就是: RDB 是 定期备份, 会定期的将内存中的数据备份到硬盘上. 而 AOF 是 实时备份, 只要 redis 数据库中的数据发生改变, 就会触发实时备份.

接下来会分别介绍两种持久化策略中的细节

RDB

RDB 持久化是定期地把当前的数据生成快照保存到硬盘的过程

触发机制

RDB 的触发时机有两种, 一种是自动触发, 一种是手动触发

手动触发: 通过 redis 客户端执行 save 或者 bgsave 命令来触发

  • save: 执行 save 命令的时候, redis 就会全力以赴地进行生成快照的操作, 此时就会阻塞 redis 客户端的其它命令, 直至快照生成完毕. (这个命令在整个过程会对 redis 服务器造成长时间的阻塞, 导致其它客户端的命令无法在第一时间被执行, 所以基本不采用)
  • bgsave: redis 进程执行 fork 操作创建出一个子进程, RDB 操作由子进程来进行负责. (由于 RDB 操作由子进程来负责, 所以父进程只在执行 fork 操作的时候进行了短时间的阻塞, 并不会阻碍父进程的执行)

自动触发: 在配置文件中配置自动触发的选项, 当达到一定的条件时, 会自动触发 RDB

RDB 自动触发的条件:

  1. 在配置文件中配置 save m n(意思是只要 m 秒内数据发生 n 次修改, 就会自动触发 RDB 持久化)
  2. redis 进行主从复制的时候, 会自动触发 RDB 持久化操作, 并将 RDB 文件内容发送给从结点
  3. 执行 shutdown 命令手动关闭 redis, 此时会自动触发 RDB 持久化操作

只要达到上述条件的其中一个, 就会触发 RDB 操作

bgsave 的运作流程

bgsave 是主流的 RDB 持久化操作, 其运作流程如下图所示:

上图中, 父进程为 redis 服务器, 在执行 bgsave 命令之后会以如下步骤来执行:

  1. 父进程会先判定时候已经有其它正在工作的子进程 (已经在执行 bgsave 的过程中), 如果已经有了一个子进程正在执行 bgsave, 此时就把当前的 bgsave 返回.
  2. 如果没有其它正在工作的子进程, 就通过 fork 操作来创建出一个子进程 (父进程在执行 fork 操作的时候会被阻塞, 当子进程被创建完成之后, bgsave 会返回一个信息给父进程, 提示子进程创建完毕, 便不再阻塞父进程).
  3. 子进程负责生成 RDB 文件, 父进程继续接收客户端的请求提供服务.
  4. 子进程完成 RDB 持久化过程之后, 就会通知父进程, 父进程就会更新一些统计信息, 之后子进程就可以结束销毁了.
RDB 文件

redis 服务器是默认开启 RDB 的, 在 redis 配置文件中就可以看到 redis 的工作目录, 而 RDB 文件默认就是存放在 redis 的工作目录下.

来到 redis 的工作目录下 (在 Ubuntu 中默认是 /var/lib/redis) 可以看到生成的 RDB 文件

使用 vim 打开 dump.rdb 文件, 查看其中的内容

发现这个文件的内容是一串乱码. 其实这个文件是一个二进制文件, redis 为了节省空间, 把内存中的数据以压缩的形式来保存到这个二进制文件中.

之后每当 redis 服务器重新启动, 就会尝试去加载这个 .rdb 文件, 如果这个文件出现了格式错误 (例如在网络传输的过程中这个文件遭到了损坏), redis 服务器就会拒绝启动服务.
而为了检查这个文件是否是已经损坏的, redis 官方提供了 rdb 文件检查工具: redis-check-rdb 来判断这个文件是否损坏

RDB 持久化操作可以多次执行, 那么 dump.rdb 这个快照文件是否会存在多个呢?
答案是: 不会
当执行生成 rdb 文件的操作的时候, 此时就会把快照先保存到一个临时文件中, 当这个快照快生成结束的时候, 删除之前的 rdb 文件, 并将这个临时文件的名字修改成 dump.rdb. 所以自始至终, rdb 文件都是只有一个的.

RDB 的优缺点

优点:

  1. RDB 是一个紧凑压缩的二进制文件, 代表 redis 在某个时间点的数据快照. 非常适用于备份, 全量复制的场景.
  2. redis 服务器加载 RDB 文件恢复数据远远快于 AOF 的方式
    缺点:
  3. RDB 方式没法做到实时持久化, 当主机掉电的时候 RDB 备份的数据往往不是完整的, 存在着数据丢失的情况
  4. RDB 文件使用特定的二进制格式来保存, redis 多个版本中的 rdb 文件可能不能互相兼容

AOF

了解完 RDB 进行持久化操作之后, 可以看出, RDB 最大的问题就是不能实时的持久化保存数据, 在两次生成快照之间, 数据可能会随着异常的因素(服务器掉电, 程序崩溃…)而丢失. 而这个问题可以通过 AOF 来解决.

AOF 进行持久化的流程类似于 MySQL 中的 binlog, 会把用户的每个操作都记录到文件中, 当redis重新启动的时候, 就会读取 AOF 文件的内容用来恢复数据

当 RDB 和 AOF 同时开启的时候, 就会生成两个备份文件, 那么应该怎么读取?
当启动 AOF 的时候, RDB 就不生效了, 启动的时候就不会读取 RDB 文件的内容, 而是读取 AOF 文件的内容

开启 AOF

在 redis 中, AOF 默认是关闭的, 需要通过修改配置文件来开启 AOF 功能

如上图, 将 appendonly 之后的配置项由 no 改成 yes 之后, 就能开启 AOF 功能.
下面的 appendfilename 配置项可以修改 aof 文件的名字, 默认是 appendonly.aof

配置完文件之后重新启动 redis, 可以看到在工作目录下生成了 aof 文件

在客户端插入几个 key 之后, 查看这个 aof 文件

可以看到这个文件中就会存放之前对 redis 的操作

AOF 的工作流程

引入 AOF 机制之后会有一个问题: 我们知道 AOF 是会将客户端的操作写到文件中, 这样既要写内存又要写文件, 会影响到redis处理请求的速度吗?
答案是: 不会造成太大影响

  • AOF 机制并非是直接让工作线程直接把数据写入硬盘, 而是先写入一个内存中的缓冲区, 积累一波之后再统一写入硬盘, 这样会大大降低写硬盘的次数.
  • 在硬盘上读写数据也是有快慢之分的, 顺序读写的数据是比较快的, 而随机访问的速度则比较慢. AOF 机制是每次把新的操作写入到文件的末尾, 属于顺序写入, 效率较高.

综上所述, 开启 AOF 功能并没有对 redis 处理请求造成太大影响.

AOF 的工作流程如下图所示:

  1. 所有的写入命令会追加到 aof_buf(缓冲区) 中
  2. AOF 缓冲区根据对应的策略向硬盘做出同步操作
  3. 随着 AOF 文件越来越大, 需要定期对 AOF 文件进行重写, 达到压缩的目的
  4. 当 redis 服务器启动的时候, 可以加载 AOF 文件进行数据恢复
AOF 缓冲区同步文件策略

假设有这么一种情况: 当客户端对 redis 进行操作之后, AOF 机制会把操作记录在 aof_buf 中, 如果此时服务器突然宕机, 这个缓冲区中的数据还未同步到 aof 文件中, 这时这份数据就丢失了.

此时就出现了矛盾: 引入了 aof_buf 缓冲区, 提高了效率, 但是在某些情况下就支撑不起数据的可靠性了, 就如同 MySQL 中的事务的隔离性一样, 对数据的效率和准确性做出取舍.

redis 给出了一些选项, 能让程序员在实际情况做出取舍, 这就是缓冲区同步文件策略:

  • 同步频率越高, 性能影响就越大, 同时数据的可靠性就越高
  • 同步频率越低, 性能影响就小, 数据的可靠性就越低
可配置项说明
always命令写入 aof_buf 后调用 fsync同步, 完成后返回
everysec命令写入 aof_buf 后只执行 write 操作, 不进行 fsync, 每秒由同步线程进行 fsync
no命令写入 aof_buf 后只执行 write 操作, 由 os 控制 fsync 的频率

可以看到,

  • always 是每次数据操作都进行同步, 是频率最高, 数据最可靠, 但是性能最低的选项
  • everysec(redis 默认配置项) 是每秒进行同步, 数据的可靠性对比 always 来说会降低, 但是性能会提高
  • no 是频率是最低的选项, 数据可靠性也是最低的, 性能是最高的
AOF 文件重写机制

随着 redis 启动时间的增长, 里面的数据不断变多, 这会导致 AOF 文件的持续增长, 文件的体积会变得越来越大, 虽然说硬盘资源是非常便宜的, 但是最主要的问题就是 redis 重新启动的时候会读取 aof 文件中的内容, 这个文件的体积过大就会使得 redis 在启动的时候需要花更多的时间来读取 aof 文件, 这就导致 redis 的启动速度会变慢.

AOF 机制的特性就是会将用户每个操作都写入 aof 文件中, 这个特性天生就有一个问题: 文件中的许多操作是冗余的.

想象一下一种情况, 当 redis 中的一个 key 存放的是短视频的播放量, 当有许多用户观看视频的时候, redis 客户端的操作可能是:

incr views
incr views
incr views
incr views

这些操作实际上是十分冗余的, 这会导致 aof 文件体积在短时间内迅速增长
这个文件记录了许多中间过程, 实际上 redis 在重新启动的时候只关注最终的结果, 这些中间的过程全都是冗余的.

因此 redis 就存在一个机制, 能针对 aof 文件进行 “整理” 操作, 能够剔除其中的冗余操作, 并且合并一些操作, 达到给 aof 文件 “瘦身” 的效果. 这个机制就是 AOF 的重写机制.

AOF 重写过程可以手动触发也可以自动触发:

  • 手动触发: 在客户端调用 bgrewriteaof 命令
  • 自动触发: 在配置文件中配置 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 参数来确定触发时机
    • auto-aof-rewrite-min-size: 表示触发重写时 aof 的最小文件大小, 默认为 64mb
    • auto-aof-rewrite-percentage: 表示当前 aof 占用大小相比较上次重写时增加的比例
AOF 文件重写流程

跟 RDB 持久化机制类似, AOF 文件重写机制仍然是用 fork 操作创建出子进程, 父进程仍然负责接收请求, 子进程负责对 aof 文件进行重写. 同样的, 重写的时候, 也不关心 aof 文件中原来都有啥, 只关心内存中数据的最终状态. 子进程只要把内存中当前的数据获取出来, 以 AOF 的格式写入到一个新的 AOF 文件中即可.

AOF 文件重写流程如下图所示:

从图中可以看出来:
3.1) 操作中子进程在写新的 aof 文件的同时, 父进程仍然在不停地接收客户端的请求, 父进程还是会把这些请求的操作写入到 aof_buf 中, 再刷新到原有的 AOF 文件里.

3.2) 和 4) 操作也是同时进行的, 在用 fork 操作创建出子进程的一瞬间, 子进程就继承了当前父进程的内存状态, 因此, 子进程里的内存数据是父进程 fork 之前的状态, 而在 fork 之后父进程接收到的请求子进程是不知道的. 所以, 父进程这里专门准备了一个 aof_rewrite_buf 缓冲区, 专门存放 fork 之后的请求操作. 在同一时刻子进程也在把 fork 前的操作写入到新 AOF 文件中.

5.1) 中子进程在写完 aof 数据之后, 会通过信号通知父进程, 然后再进行 5.2), 父进程把 aof_rewrite_buf 缓冲区中的内容也写入到新 AOF 文件里.

最后是 5.3) 操作, 将新的 AOF 文件代替旧的 AOF 文件.

  1. 如果在执行 bgrewriteaof 的时候, 当前 redis 已经在进行 aof 文件重写了, 会怎么样?
  • 此时不会再次执行 aof 重写, 而是直接返回
  1. 如果在执行 bgrewriteaof 的时候, 当前 redis 在生成 rdb 文件的快照, 会怎么样?
  • 此时 aof 重写操作就会等待, 等待 rdb 快照生成完毕之后, 再执行 aof 重写
  1. 父进程在 fork 之后就已经让子进程写新的 aof 文件了, 随着时间的推移, 当子进程写完了新的文件之后就要让新的 aof 文件替代旧的 aof 文件, 父进程此时还在写即将被替代的旧 aof 文件还有意义吗?
  • 非常有意义!! 假设子进程在写新的 aof 文件的时候, 服务器宕机了, 子进程内存中的数据就会丢失, 新的 aof 文件内容还只是半成品, 如果父进程不坚持写旧 aof 文件, 重启之后就无法保证数据的完整性了.

RDB 和 AOF 混合持久化机制

在 AOF 执行重写操作的时候, redis 会默认采用混合持久化机制, 可以在配置文件中看到这个选项(默认是开启的)

混合持久化的方式结合了 RDB 和 AOF 的特点, 其表现在:

  • 平时用户操作数据库的时候, 会按照 AOF 的操作, 把每一个操作都记录在 aof 文件中.
  • 在触发 AOF 重写的时候, 就会把当前的内存中的状态按照 RDB 的二进制格式写入到新的 AOF 文件中
  • 后续再进行操作, 仍然是按照 aof 文本的方式追加到文件的后面.

其表现如下列所示:
当未执行重写之前 aof 文件的内容:
可以看到, 当前的文件内容是以 aof 文件的格式来写入的

当刚执行 bgrewriteaof 操作之后:
可以看到, 当前的文件内容是以二进制的方式写入的

在重写之后, 继续对数据库中的数据进行修改:
可以看到, 当前的文件内容上半部分是以二进制的方式写入的, 而重写后的文件内容是以 aof 文件的格式来写入的

redis 启动时数据的恢复

当 redis 上同时存在了 rbd 快照和 aof 文件, 此时以谁为主?
redis 在启动并复数据时会执行一套流程, 如下图所示:

可以看到, redis 重启恢复数据时是以 AOF 为主的, 因为 AOF 中的数据比 RDB 的数据更全面.

小结

  • redis 提供了两种持久化方案: RDB 和 AOF.
  • RDB 视为是内存的快照, 内容更加紧凑, 占用空间较小, 恢复数据时速度更快, 但是产生 RDB 的开销较大, 不适合实时持久化, 一般用于备份和主从复制.
  • AOF 视为对修改命令的保存, 在数据恢复时需要重放命令, 并且有重写机制来定期压缩 aof 文件, 压缩方式为 RDB 和 AOF 混合持久化.
  • RDB 和 AOF 都使用 fork 创建子进程, 利用子进程拥有父进程内存快照的特点进行持久化, 尽可能的不影响 redis 主进程处理命令.
  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值