Redis的持久化原理分析一些大家都知道的问题RDBA

Redis持久化,老八股文,基本大家都能答出来,这里对原理进行分析。

一些大家都知道的问题

为什么要持久化?

  • Redis是内存数据库,宕机后数据会消失。
  • Redis重启后快速恢复数据,要提供持久化机制。
  • Redis持久化是为了快速的恢复数据而不是为了存储数据

持久化的方式

  • Redis的持久化机制有两种,快照(RDB)和AOF日志。

那这两种持久化方式有什么区别呢?

  • 快照是一次全量备份,AOF日志是连续的增量备份。
  • 快照是内存数据的二进制序列化形式,在存储上非常紧凑,而AOF日志记录的是内存数据修改的指令记录文本。

上面说到AOF日志记录的是内存数据修改的指令记录文本,长期的运行过程中日志记录越来越多怎么办?

  • 需要定期进行AOF重写,给AOF日志进行瘦身。

下面具体谈一下RDB和AOF

RDB

RDB(Redis DataBase),是redis默认的存储方式,RDB方式是通过快照完成的。
快照保存这一刻的数据,不关注过程。

触发快照的方式

  1. 符合自定义配置的快照的规则

配置参数定期执行
在redis.conf中配置:save 多少秒内 数据变了多少

#save "" # 不使用RDB存储 不能主从 
save 900 1 # 表示15分钟(900秒钟)内至少1个键被更改则进行快照。 
save 300 10 # 表示5分钟(300秒)内至少10个键被更改则进行快照。 
save 60 10000 # 表示1分钟内至少10000个键被更改则进行快照。
复制代码
  1. 执行save或bgsave命令
  2. 执行flushall命令
  3. 执行主从复制操作(第一次)

RDB原理

执行流程

image.png

  1. Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof(aof文件重写命令)的子进程,如果在执行则bgsave命令直接返回。
  2. 父进程执行fork(调用OS函数复制主进程)操作创建子进程,这个复制过程中父进程是阻塞的, Redis不能执行来自客户端的任何命令。
  3. 父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令。
  4. 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换。 (RDB始终完整)
  5. 子进程发送信号给父进程表示完成,父进程更新统计信息。
  6. 父进程fork子进程后,继续工作

从上面的流程中,我们可以知道,Redis是一边持久化,一边响应客户端的请求。持久化的同时,内存数据结构还在改变。比如一个大型的hash字典正在持久化,结果一个请求过来把它给删了,可是还没持久化完,这要怎么处理呢?

这个时候会使用操作系统的COW(Copy On Write)机制来进行数据段页面的分离。

如下图,数据段是由很多操作系统的页面组成,当父进程对其中一个页面的数据进行修改时,会将共享的页面复制一份分离出来,然后对这个复制的页面进行修改,这时子进程相应的页面是没有发生变化的,还是进程产生那一瞬间的数据。

image.png
随着父进程修改操作的持续进行,越来越多的共享页面被分离出来,内存会持续增长,但是也不会超过原有数据内存的2倍大小。

RDB的优缺点

优点

  • RDB是二进制压缩文件,占用空间小,便于传输(传给slaver)
  • 主进程fork子进程,可以最大化Redis性能

缺点

  • 不保证数据完整性,会丢失最后一次快照以后更改的所有数据

AOF

AOF(append only file)持久化方式。默认情况下是不开启的。

Redis 将所有对数据库进行过写入的命令(及其参数)(RESP)记录到 AOF 文件, 以此达到记录数据库状态的目的,这样当Redis重启后只要按顺序回放这些命令就会恢复到原始状态了。

AOF会记录过程,RDB只管结果

AOF持久化触发

配置 redis.conf

# 可以通过修改redis.conf配置文件中的appendonly参数开启 
appendonly yes
复制代码

AOF持久化原理

AOF持久化流程
image.png
流程解读

命令传播

当一个 Redis 客户端需要执行命令时, 它通过网络连接, 将协议文本发送给 Redis 服务器。服务器在接到客户端的请求之后, 它会根据协议文本的内容, 选择适当的命令函数, 并将各个参数从字符串文本转换为 Redis 字符串对象( StringObject )。每当命令函数成功执行之后, 命令参数都会被传播到AOF程序。

缓存追加

当命令被传播到AOF程序之后, 程序会根据命令以及命令的参数, 将命令从字符串对象转换回原来的协议文本。协议文本生成之后, 它会被追加到 redis.h/redisServer 结构的 aof_buf 末尾。

redisServer 结构维持着 Redis 服务器的状态, aof_buf 域则保存着所有等待写入到 AOF文件的协议文本(RESP)。

文件保存

每当服务器常规任务函数被执行、 或者事件处理器被执行时, aof.c/flushAppendOnlyFile 函数都会被调用,

这个函数执行以下两个工作:

  • WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。
  • SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

AOF保存模式

Redis目前支持三种AOF保存模式。可以在redis.conf配置,分别是

# 每执行一个命令保存一次 
#appendfsync always 
# 每一秒钟保存一次 
appendfsync everysec 
# 不保存 
#appendfsync no
复制代码

不保存

在这种模式下, 每次调用flushAppendOnlyFile函数, WRITE 都会被执行, 但 SAVE 会被略过。
SAVE 只会在以下任意一种情况中被执行:

  1. Redis 被关闭
  2. AOF 功能被关闭
  3. 系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)

这三种情况下的 SAVE 操作都会引起 Redis 主进程阻塞。

每一秒钟保存一次(推荐)

在这种模式中, SAVE 原则上每隔一秒钟就会执行一次, 因为 SAVE 操作是由后台子线程(fork)调用 的, 所以它不会引起服务器主进程阻塞。

每执行一个命令保存一次

在这种模式下,每次执行完一个命令之后, WRITE 和 SAVE 都会被执行。

另外,因为 SAVE 是由 Redis 主进程执行的,所以在 SAVE 执行期间,主进程会被阻塞,不能接受命令请求。

三种模式对比

image.png

AOF重写

Redis在长期运行的过程中,AOF的日志会越来越长。如果实例宕机重启,重放整个AOF日志会非常耗时,导致Redis长时间无法对外提供服务,所以需要对AOF日志进行瘦身。

重写原理

主进程开辟一个子进程对内存进行遍历,转换一系列Redis的操作指令,序列化到一个新的AOF日志文件中。

但是这个过程主进程会不断的产生新的数据,这些数据怎么加到新的AOF日志文件汇中呢?

为了解决这个问题,Redis增加了一个AOF重写缓存,这个缓存在fork出子进程之后开始启用。Redis主进程在接到新命令之后,除了会将这个命令的协议内容追加到现有的AOF文件之外,还会加到这个缓存中。

子进程生成新的AOF文件后,新产生的增量AOF会追加到这个新的AOF文件中,然后用新的AOF文件替换旧的AOF文件。

流程图如下:

image.png

整个重写过程是绝对安全的

Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生 停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。

AOF重写触发方式

  1. 配置触发

在redis.conf中配置

# 表示当前aof文件大小超过上一次aof文件大小的百分之多少的时候会进行重写。如果之前没有重写过, 以启动时aof文件大小为准 
auto-aof-rewrite-percentage 100 
# 限制允许重写最小aof文件大小,也就是文件大小小于64mb的时候,不需要进行优化 
auto-aof-rewrite-min-size 64mb
复制代码
  1. 执行bgrewriteaof命令
127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started
复制代码

混合持久化

重启Redis时,我们很少使用RDB来恢复内存状态,因为会丢失大量数据。那么使用AOF日志重发就完美了吗?

重发AOF日志相对于使用RDB来说要慢很多,这样在Redis实例很大的时候,启动需要花费很长的时间。

Redis4.0给我们解决了这个问题,混合持久化

如果把混合持久化打开,AOF重写的时候就直接把 rdb 的内容写到 aof 文件开头。再把AOF增量日志追加到文件末尾

开启混合持久化

aof-use-rdb-preamble yes
复制代码