Loading

走进Redis - 数据持久化

Redis 的数据持久化机制

Redis 的持久化主要有两大机制:AOF(Append Only File) 日志和 RDB 快照。

AOF

AOF 记录 Redis 的操作日志,它是在主线程中执行的。AOF 和数据库的写前日志(Write Ahead Log, WAL)不同,WAL 是在执行命令前先把修改的数据写到日志文件中,而 AOF 是在命令执行成功之后再记录日志,这样有两个好处:

  1. 可以减少额外的检查命令是否正确的开销。
  2. 因为是在命令执行后记录,所以不会阻塞当前的写操作。

AOF 日志的格式

AOF 中的命令是以文本形式记录的,以 “set testkey testvalue” 为例:Redis 会先以 *3 来表示当前命令有三个部分,每部分会先以 $+数字 来表示长度,然后跟上具体的命令、键或值。例如:

潜在风险

  1. 可能在命令执行之后,写入 AOF 之前机器宕机,那么相应数据就有丢失的风险。
  2. 上面说到 AOF 是在主线程执行的,如果磁盘写入很慢,那么就有可能阻塞下一个命令的执行。

写回策略

根据上述的潜在风险,Redis 提供了三种写回策略供用户评估选择,可以在 AOF 配置项 appendfsync 配置:

配置项 写回时间 优点 缺点
Always 同步写回 可靠性高,数据基本不丢失 每个写命令都要落盘,性能影响较大
Everysec 每秒写回 性能适中 宕机时丢失1秒内的数据
No 操作系统控制的写回 性能好 宕机时丢失数据较多

AOF 重写机制

背景

当 Redis 接收的命令越来越多,那么 AOF 文件就会越来越大,AOF 文件过大带来性能问题:

  1. 文件系统本身对文件大小有限制,无法保存过大的文件;
  2. 文件过大时,追加命令记录效率会变低;
  3. 如果发生宕机,AOF 中的记录要一个个回放恢复数据,文件过大时会导致这个过程非常缓慢,影响 Redis 的正常使用。

重写机制

重写机制是指 Redis 会根据当前的数据状态创建一个新的 AOF 文件,即读取当前 Redis 中的所有的记录,将其一一写入新的 AOF 文件中。

为什么重写机制可以缩小 AOF 文件的大小呢?正常使用过程中,使用者可能会对一个 key 进行多次操作,而这些操作都会被记录到 AOF 中,而重写机制会忽略这些过程,只将最后的结果记录到 AOF 中,多条记录合并成了一条。

重写过程和 AOF 日志不同,它是由后台子进程 bgrewriteaof 来完成的,这样可以避免阻塞主线程导致的性能下降。

重写过程的主要流程如下:

  1. 每次执行重写时,主线程会 fork 一个 bgrewiteaof 子进程,因为 fork 的特性,同时会把主进程的内存拷贝一份给子进程。
  2. 因为此时主线程没有被阻塞,AOF 日志还会正常执行。
  3. 主线程的操作还会被记录到重写缓冲区中,等拷贝数据重写完成后,重写缓冲区的日志也会被写入新的 AOF 文件,都完成后,新的 AOF 文件会替代就文件。

💡 其实,重写机制是会阻塞主线程的,这发生在 fork 子进程的时候,因为这时候需要拷贝主线程的数据结构,包括内存页。这个过程会消耗大量的 CPU 资源,并且主线程是阻塞的,阻塞的时间取决于整个实例的内存大小。同时,因为 Linux 的 fork 是写时赋值,当主线程接收请求,操作了一个已经存在的数据,父进程会重新为该数据分配内存,如果操作的是一个 bigkey,重新申请内存的时间就会变长,从而产生阻塞的风险。Linux 上分配内存是以内存页为单位的,默认大小为 4k,当开启了大页机制(Big Huge 页面大小 2M)时,阻塞的概率会大大提高,所以,Redis 机器上最好关闭 Huge Page 机制。

RDB

RDB 是 Redis 提供的一种内存快照,用来记录一段时间内 Redis 中的数据。和 AOF 相比,RDB 记录的是数据而不是操作,所以可以直接将 RDB 文件读入内存恢复数据。

快照机制

Redis 提供了两个命令来生成 RDB 文件:

  1. save:在主线程执行,会导致阻塞;
  2. bgsave:在子进程执行,避免阻塞,是默认的选择。

bgsave 和 AOF 的重写机制一样,是 fork 一个子进程来执行的,而 fork 之后的内存是写时拷贝的,当主线程接收到修改数据的命令时,会拷贝一份数据进行修改。这样既不会影响 bgsave 对旧的数据做快照,也不会影响主线程对数据进行修改。

优化

快照的意义在于备份数据库的数据,当两次备份中服务器发生了宕机,那么就可能会导致数据丢失,这时就会要求两次快照之间的时间间隔足够短。如果在短时间内执行全量快照可能会带来两方面的开销:

  1. 频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争优先的磁盘带宽,前一个快照还没做完,后一个就开始了,容易造成恶性循环;
  2. 和 bgrewiteaof 一样,fork 子进程可能会有阻塞主线程的风险,频繁执行全量快照会将这个风险放大。

一个解决方式是增量快照,即记录两次快照间的数据变化,下一次做快照只针对这些变化的数据做记录。在内存中记录数据变化是现实的,会造成大量的内存开销。Redis 的解决方案是混用 AOF 日志和 RDB,由 AOF 来记录两次内存快照间的数据变化。这样,快照不需要频繁执行,避免了频繁 fork 对主线程的影响,而 AOF 也只用记录两次快照间的数据,不会出现文件过大的情况,避免重写开销。

颇有两全其美的感觉。

总结

  1. 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择;
  2. 如果允许分钟级的数据丢失,可以只是用 RDB。
  3. 如果只用 AOF,优先使用 everysec 的配置选项。
posted @ 2022-08-03 21:18  LooJee  阅读(79)  评论(0编辑  收藏  举报