七的博客

Redis持久化机制研究

原理分析

Redis持久化机制研究

最近在使用 Redis 的过程中,简单的研究了下 Redis 是通过什么机制来实现数据持久化的。感兴趣的点在下面几个:

  • AOF 以及 RDB 持久化各是什么?
  • 两种机制持久化到文件的频率是怎么样的? 持久化机制是什么?
  • 两种机制生成的文件内容长什么样?
  • 综合对比下两种方案的优缺点。

在目前的版本中,Redis 是通过 AOF 以及 RDB 这两种机制来实现数据持久化。

1. AOF 持久化

AOF 的全称是 Append Only File 。 Redis 会将收到的写命令先执行,把数据写到了内存里面,再把写的命令追加到 AOF 文件的末尾。

执行顺序是: Redis 收到写命令 > Redis 执行命令 > Redis 命令执行完成 > Redis 将当前命令写入 AOF 文件

AOF 文件里面存的内容就是收到的命令文本字符串,直接查看可以看到类似下面这种文本:

*3
$3
SET
$4
key1
$5
value

上面就是一串标准的 RESP 协议报文,实际含义就是一条写命令 SET key1 value 。关于报文的解释之前有写过一篇文章 《 Redis客户端跟服务端通信协议》 单独讲解过。

AOF 持久化需要在 Redis 配置文件 redis.conf 中开启:

# appendonly 默认是 no。  改为 yes 就可以开启 AOF 持久化。
appendonly yes

# 下面这个配置可以更改 AOF 文件名, 默认是 "appendonly.aof"
appendfilename "appendonly.aof"

1.1 写入 AOF 文件的频率

根据 AOF 持久化机制的特点,执行完一条写命令后才会将命令写入 AOF 文件。那么肯定写入的频率也是可以控制的,当前 Redis 提供了一个参数 appendfsync 来控制这个写入的频率,共有下面三种方案:

# 每个写操作都会立即同步到 AOF 文件,最安全但性能较差。
appendfsync always

# 每秒同步一次,性能跟数据安全的一个折衷方案。 通常都是推荐这种方案。
appendfsync everysec

# 由操作系统决定何时同步,最快但最不安全。 性能最好但是不安全可能丢失数据。
appendfsync no

这三种方案也是比较好理解,就是在性能跟安全上做个取舍。三种策略都无法做到很完美。

如果你要数据更安全,那就选择 always 这种方案,每一次都会同步保存,丢数据的概率最小。但是操作磁盘的速度比操作内存的速度慢很多,这个对性能影响很大。

如果更在意性能,不那么注重数据安全,那就把同步频率交给操作系统控制。 如果需要在性能跟数据安全之间取个平衡,那么就选择 everysec 这种方案。everysec 是每秒同步一次,所以就算丢数据,也就是最多丢一秒钟的数据。

1.2 AOF 文件变大了怎么处理

上面提到的每一次写命令都会写入到 AOF 文件中去,如果运行时间久了,AOF 文件会变得越来越大。

往磁盘文件写入内容的时候,如果文件很大的话,会影响写入效率的。 同时 AOF 文件太大也会 Redis 重启后恢复的速度,毕竟恢复的时候需要一条条执行命令。 而且因为有前后顺序关系,可能还不好做并行恢复,只能使用串行执行恢复。

这种情况下 Redis 也有策略来应对,这种策略成为 AOF 重写。默认情况下, Redis 会通过参数中配置的重写条件来决定要不要重写。

AOF 重写简单的来说,就是把当前 Redis 里面的数据全部转换成一个个写入的命令,然后全部写入到一个临时的 AOF 文件中去。 这个重写的过程是通过一个子进程来实现的,子进程负责对这个拷贝的数据做处理。

当新的 AOF 文件生成的时候, Redis 会将新的 AOF 文件替换旧的文件。 这样就可以解决 AOF 文件过大的问题。

1.3 AOF 重写过程中新的写命令怎么处理

上面说的 AOF 重写是由子进程去执行的,子进程拿到的是一份数据快照。 这个时候主进程依旧可以接收客户端的写命令请求,那就会引发一个问题,新写入的命令怎么处理。

Redis 的处理方法是,在重写过程中,新的写入命令会暂存在一个缓冲区里面。 AOF 文件重写完成之后,Redis 将缓冲区的这一部分新的写命令追加到 AOF 文件末尾去,就实现了命令的合并。

这个过程主线程继续处理请求,子线程负责 AOF 文件重写,互相不会影响。

1.4 AOF 优缺点

优点:

  • 安全性更早,因为是定时去持久化的。
  • 可读性好,分析的时候更友好。

缺点:

  • 生成的 AOF 文件体积大,不支持压缩。
  • 恢复速度慢,需要一条条写命令执行。
  • 持久化频率高对性能影响大。

2. RDB 持久化

RDB 的全称是 Redis Database , Redis 会定期把内存里面的全量数据生成一个快照。 然后这个快照会写入到硬盘上,当 Redis 重启的时候,可以通过加载这个快照文件来快速恢复数据。

RDB 文件是一个二进制文件,所以无法直接进行查看。跟 AOF 持久化不一样的地方在于,RDB 持久化是通过特定的条件触发写入的,这个不需要人为去控制。 如果想手动触发生成也可以调用 Redis 的 save 或者 bgsave 命令来触发写入。

2.1 RDB 的特点

上面 AOF 原理探究的时候也提到了,AOF 文件里面存的是一条条写入操作的命令,所以都是一些文本信息。 正常情况下,文本信息都是存在很多重复的字符的,所以在空间占用上会比较高。 RDB 文件是二进制的压缩数据,占用的空间小。 而且因为数据文件里面不是一条条的命令,所以恢复的时候不需要一条条命令去执行恢复。

可以总结下 RDB 文件的优点:

  • 数据文件压缩比高,文件大小比较小。
  • 恢复速度很快。

2.1 什么时候会触发写入 RDB 文件

RDB 文件的持久化也是按照一定频率去控制的,高频生成 RDB 对性能的特别大。

触发 RDB 写入的频率可以在 redis.conf 中控制:

# 每 900 秒至少有 1 个键发生了变化,触发 RDB 保存。
save 900 1

# 每 300 秒至少有 10 个键发生了变化,触发 RDB 保存。
save 300 10       

# 每 60 秒至少有 10000 个键发生了变化,触发 RDB 保存。
save 60 10000     

2.2 RDB 文件的生成流程

Redis 目前有 2 个命令来生成这个 RDB 文件,分别是 save 命令跟 bgsave 命令。

  • save 命令是在主线程中执行,会导致主线程阻塞。 这个时候主线程就没有办法处理其他请求操作了。
  • bgsave 命令创建一个子进程来负责写入 RDB 文件。 这样可以避免主线程的阻塞,默认情况下 Redis RDB 文件就是通过这种方式生成。

首先是 Redis 主进程会 fork 出一个子进程来,这个子进程是父进程的副本。 两个进程有相同的内存空间副本,但在执行时是独立的。 再一个就是父子进程共享相同的物理内存,只有在有写入操作时才会复制内存页,这可以减少内存使用。 不然的话,假设 Redis 中有 4GB 数据, fork 出一个子线程来,内存占用就会变成 4 + 4 = 8GB 。

子进程将内存中的数据都读取完,将数据保存到新的临时 RDB 文件中。当这个新的临时文件生成完毕后就会替换原有的 RDB 文件,这样就完成了数据的持久化。

2.3 RDB 的缺点

  • RDB 是通过特定条件去触发生成的,所以可能会丢失上一次快照之后的数据。
  • 数据量大的时候, fork 子进程的过程在主进程里面去进行的,会造成 Redis 服务短暂无响应。

参考链接