Redis持久化

Redis的读写操作均是在内存中,当Redis重启后,内存中的数据就会丢失;为了防止数据丢失,Redis实现了数据持久化的机制,该机制会将数据存储到磁盘中,这样Redis重启时就能够从磁盘中恢复原有的数据。

Redis共有三种实现数据持久化的方式:

  • AOF日志:每执行一条写操作命令,就把该命令以追加的方式写入到日志文件里;
  • RDB快照:将某一时刻的内存数据,以二进制的方式写入磁盘;
  • 混合持久化:Redis 4.0新增的方式,集成了AOF和RDB的优点

AOF日志

Redis 在执行完一条写操作命令后,就会把该命令以追加的方式写入到一个文件里,然后 Redis 重启时,会读取该文件记录的命令,然后逐一执行命令来进行数据恢复。

AOF

例如,发送命令为set name Mitsui,AOF记录日志如图:

AOFLog

其中,*3表示命令有三个部分,每个部分都是$+数字开头,后面紧跟具体的命令、键、值;该数字表示紧跟的命令、键或者值长度为多少字节,例如$3 set表示set命令的字符串长度。

为什么先执行命令,再写入日志呢?

这样做有两个好处:

  • 避免额外的检查开销:因为如果先将写操作命令记录到 AOF 日志里,再执行该命令的话,如果当前的命令语法有问题,那么该错误的命令记录到 AOF 日志里后,Redis 在使用日志恢复数据时,就可能会出错。
  • 不会阻塞当前写操作命令的执行:因为当写操作命令执行成功后,才会将命令记录到 AOF 日志。

但也会有若干风险:

  • 数据可能会丢失: 执行写操作命令和记录日志是两个过程,那当 Redis 在还没来得及将命令写入到硬盘时,服务器发生宕机了,数据就会有丢失的风险。
  • 可能阻塞其他操作: 由于写操作命令执行成功后才记录到 AOF 日志,所以不会阻塞当前命令的执行,但因为 AOF 日志也是在主线程中执行,所以当 Redis 把日志文件写入磁盘的时候,还是会阻塞后续的操作无法执行。

Redis写入AOF日志的过程如图所示:

RedisAOF
  • Redis执行完写操作命令后,会将命令追加到server.aof_buf缓冲区内;
  • 然后通过write()系统调用,将aof_buf缓冲区的数据写入到AOF文件,此时数据并没有写入到硬盘,而是拷贝到了内核缓冲区page cache,等待内核将数据写入硬盘;
  • 内核缓冲区的数据具体什么时候写入到硬盘,由内核决定。

上述第三步中的写回操作,AOF有三种写回策略,具体参数可在Redis.conf中的appendfsync配置:

  • Always:每次写操作后,同步将AOF日志数据写回到硬盘
  • Everysec:每次写操作后,内核缓冲区每隔一秒将数据写回到硬盘
  • No:写回时机不由Redis控制,交由操作系统来决定
writeback

AOF日志过大,会触发什么机制?

AOF 日志是一个文件,随着执行的写操作命令越来越多,文件的大小会越来越大。 如果当 AOF 日志文件过大就会带来性能问题,比如重启 Redis 后,需要读 AOF 文件的内容以恢复数据,如果文件过大,整个恢复的过程就会很慢。

所以,Redis 为了避免 AOF 文件越写越大,提供了 AOF 重写机制,当 AOF 文件的大小超过所设定的阈值后,Redis 就会启用 AOF 重写机制,来压缩 AOF 文件。

AOF 重写机制是在重写时,读取当前数据库中的所有键值对,然后将每一个键值对用一条命令记录到「新的 AOF 文件」,等到全部记录完后,就将新的 AOF 文件替换掉现有的 AOF 文件。

例如:在没有重写之前,分别执行了set name Mitsuiset name Mitsui21两条命令,那么AOF日志中就会保存这两条命令;在使用重写之后,读取内存中的键值对(即name:Mitsui21),并转化成命令保存到新的AOF文件,最终覆盖掉旧的AOF文件。如此,减少了历史命令的占据空间。

AOF日志重写的过程如下:

Redis的AOF重写过程是由**后台子进程bgrewriteaof**来完成的,这样有两个好处:

  • 子进程进行AOF重写期间,主进程可以继续处理命令请求,避免阻塞主进程
  • 子进程带有主进程的数据副本,这里使用子进程而不是线程,因为如果是使用线程,多线程之间会共享内存,那么在修改共享内存数据的时候,需要通过加锁来保证数据的安全,而这样就会降低性能。而使用子进程,创建子进程时,父子进程是共享内存数据的,不过这个共享的内存只能以只读的方式,而当父子进程任意一方修改了该共享内存,就会发生「写时复制」,于是父子进程就有了独立的数据副本,就不用加锁来保证数据安全

触发重写机制后,主进程就会创建重写 AOF 的子进程,此时父子进程共享物理内存,重写子进程只会对这个内存进行只读,重写 AOF 子进程会读取数据库里的所有数据,并逐一把内存数据的键值对转换成一条命令,再将命令记录到重写日志(新的 AOF 文件)

如果主进程修改了共享内存中的数据,发生写时复制,如何解决父子进程中该数据的不一致性

Redis为此设置了一个AOF重写缓冲区,这个缓冲区在bgrewriteaof子进程创建时启用。

AOFrewrite

在重写AOF期间,当Redis执行完一个命令后,它会同时将这个写命令写入到AOF缓冲区和AOF重写缓冲区。

bgrewriteaof子进程执行AOF重写期间,主进程需完成以下工作:

  • 执行客户端发来的命令
  • 将执行后的写命令追加到AOF缓冲区
  • 将执行后的写命令追加到AOF重写缓冲区

当子进程完成重写后,向主进程发送一条信号(信号是进程间通信的一种方式,且异步)

主进程收到该信号后,调用一个信号处理函数,该函数完成以下工作:

  • 将AOF重写缓冲区中的所有内容追加到新的AOF文件中,使得新旧两个AOF文件所保存的数据库状态一致
  • 新的AOF文件改名,覆盖现有的AOF文件

RDB快照

因为 AOF 日志记录的是操作命令,不是实际的数据,所以用 AOF 方法做故障恢复时,需要全量把日志都执行一遍,一旦 AOF 日志非常多,势必会造成 Redis 的恢复操作缓慢。

为了解决这个问题,Redis 增加了 RDB 快照。RDB 快照就是记录某一个瞬间的内存数据,记录的是实际数据,而 AOF 文件记录的是命令操作的日志,而不是实际的数据。

因此在 Redis 恢复数据时, RDB 恢复数据的效率会比 AOF 高些,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。

RDB做快照会阻塞线程吗?

Redis 提供了两个命令来生成 RDB 文件,分别是 savebgsave,他们的区别就在于是否在「主线程」里执行:

  • 执行了 save 命令,就会在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程
  • 执行了 bgsave 命令,会创建一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞

Redis 的快照是全量快照,也就是说每次执行快照,都是把内存中的「所有数据」都记录到磁盘中。所以执行快照是一个比较重的操作,如果频率太频繁,可能会对 Redis 性能产生影响。如果频率太低,服务器故障时,丢失的数据会更多。

RDB在做快照时,数据能修改吗?

可以。执行bgsave过程中,Redis依然可以继续处理操作命令,也就是数据是可以修改的。关键技术就在于写时复制(Copy-On-Write)。

执行bgsave命令的时候,会通过 fork() 创建子进程,此时子进程和父进程是共享同一片内存数据的,因为创建子进程的时候,会复制父进程的页表,但是页表指向的物理内存还是一个,此时如果主线程执行读操作,则主线程和 bgsave 子进程互相不影响。

bgsave

如果主进程执行写操作,则被修改的数据会复制一份副本,然后bgsave子进程会把该副本数据写入RDB文件,在这个过程中,主进程仍然可以直接修改原来的数据。

cow

RDB快照过程存在如下问题:

  • 在主进程执行写操作修改数据时,RDB快照保存的是原本的内存数据,而刚刚修改的数据,只能交由下一次的快照
  • 如果在主进程写操作修改数据后,且下一次快照执行前,发生了崩溃,那么此次修改将会丢失
  • 极端情况下,主进程每一个操作均是写操作,那么占用的内存将是原来的2倍

混合持久化

RDB 优点是数据恢复速度快,但是快照的频率不好把握。频率太低,丢失的数据就会比较多,频率太高,就会影响性能。

AOF 优点是丢失数据少,但是数据恢复不快。

因此,Redis 4.0提出了混合持久化,即混合使用AOF日志和内存快照,既保证了Redis重启速度,又降低了数据丢失风险。

混合持久化工作在 AOF 日志重写过程,当开启了混合持久化时,在 AOF 重写日志时,fork出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

使用了混合持久化后,AOF文件的前半部分是RDB格式的全量数据,后半部分是AOF格式的增量数据。

好处在于:

  • 重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样加载速度会很快

  • 加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得数据更少丢失