Redis持久化机制

  • 2020 年 4 月 13 日
  • 筆記

博客地址://tech101.cn/2020/03/05/Redis%E6%8C%81%E4%B9%85%E5%8C%96%E6%9C%BA%E5%88%B6

前言

Redis是一款纯C语言编写的符合ANSI C标准实现的内存数据库。Redis以支持丰富的内存数据结构和高性能著称,在互联网行业中被广泛用于缓存数据和内存计算。

虽然Redis是一款内存数据库,但是它也提供了数据持久化的能力。本文,我们就来聊聊Redis的数据持久化机制。

持久化面临的问题

在正式开始介绍Redis持久化功能之前,我们先来看下实现持久化能力需要面临的一些技术问题。

当客户端请求Redis服务端将数据写入Redis数据库的时候,数据将被存放在内存中。如果Redis数据库启用了持久化功能,那么数据将被持久化到持久化设备(磁盘)上。从客户端请求服务端写入数据到数据被持久化到磁盘上,整个过程需要经历如下几个阶段1

  1. 客户端向服务端发起写命令。
  2. 服务端接收到客户端请求,执行写命令将数据写入内存。
  3. 服务端调用write()系统调用(Unix环境)将内存中的数据写入内核缓冲区。
  4. 调用fsync()将内核缓冲区的数据写入磁盘控制器的缓存中。
  5. 磁盘控制器将缓存中的数据写入到磁盘的物理介质上。

在上面列出的5个步骤中,第1步到第3步数据都在内存中存放,一旦服务crash,那么数据将永久性的丢失了。在第4步和第5步中,数据已经从内存转移到了磁盘设备上,不过在第4步中数据是被写入到了磁盘的控制器缓冲区(为了解决磁盘设备和内存设备访问延迟的差异,通过缓冲区技术来提高单位时间内设备的吞吐量)中,所以一旦服务器掉电宕机,在缓存中的这部分数据也可能将会丢失(取决于物理存储设备)。只有当数据经过第5步被写入磁盘物理介质以后,数据才算真正地被保存了下来,不会因为服务器掉电而丢失数据。

从上面的过程中我们可以发现,为了保证数据持久化过程的顺利,我们只有成功地将上面第1到5步这五个步骤同步执行完成以后,数据才算安全的被持久化下来,其中任意一步出现问题,数据都可能存在丢失的风险。

当然,要想完全的执行完上面的五个步骤是很理想的情况,实际在实现持久化机制的时候,将会面临一些现实的约束。首先,完成上述五个步骤涉及到Redis服务、操作系统以及底层存储硬件的紧密配合,对于操作系统之上的Redis服务实现者来说,要实现持久化功能,只能通过调用操作系统提供的功能(系统调用System call)来完成对底层存储硬件的访问,所以实现者能控制的只有上述的第1-4这四步,至于最后一步则可能不受Redis服务的实现者控制,由各个硬件设备自己实现(至少不能保证能提供对应的内核驱动API供操作系统访问)。所以在持久化这件事上,实现者能做的是保证第1-4步能顺利完成。

其次,第3步和第4步都需要调用系统调用,调用系统调用会导致进程用户态和内核态的切换,这个过程是有性能损失的。频繁的调用系统调用将会降低服务的性能,而Redis作为一款高性能的内存数据库,服务的性能也是需要重点考虑的一个指标。所以为了平衡好数据安全性和性能,在实现持久化机制的时候需要作出取舍。

Redis的实现者在实现持久化的时候,为了兼顾性能和数据安全性,引入了两种持久化方案:

  1. RDB持久化
  2. AOF持久化

下面,我们将介绍这两种持久化方案。

RDB持久化

RDB持久化是Redis引入的一种数据安全性相对弱的持久化方案,通过异步将内存数据库的快照写入持久化文件来实现数据的持久化。由于生成快照是异步进行的,所以快照不会实时反应内存中的数据库情况,因此它是一种数据安全性较弱的数据持久化方案。不过由于创建快照过程是异步进行的,在创建快照过程中基本不会对内存数据库的操作产生影响,所以RDB持久化方案是一种注重性能,但是在数据安全性方面做出妥协的持久化方案。

下面,我们来看下RDB持久化的实现原理。

生成RDB快照

Redis提供了两个命令:SAVEBGSAVE来创建RDB快照文件。这两个命令的最大区别是:SAVE命令在生成RDB文件的时候会阻塞Redis的进程;而BGSAVE会创建一个子进程来生成RDB文件,不会阻塞服务器的进程。

127.0.0.1:6379> SAVE
OK

127.0.0.1:6379> BGSAVE
Background saving started

由于SAVE命令是通过阻塞服务器的进程来进行快照生成的,所以在生成快照期间服务器将拒绝来自服务器外部的请求。而BGSAVE命令通过创建子进程实现快照的生成,所以在生成快照期间Redis服务可以继续执行客户端的请求。不过需要注意的一点是:在BGSAVE命令执行期间,BGSAVESAVEBGREWRITEAOF命令的执行将会受到限制。

首先,在BGSAVE命令执行期间,SAVE命令会被拒绝执行;其次,在BGSAVE命令执行完成前,新的BGSAVE命令也会被拒绝执行;最后,对于AOF重新命令BGREWRITEAOF,在BGSAVE命令执行期间,该命令将会被延后执行。同时,如果在BGSAVE命令执行之前有BGREWRITEAOF命令正在执行,则BGSAVE命令也需要等到AOF重新命令完成以后才能被执行。这么做是考虑到持久化是一个消耗IO资源的操作,虽然BGSAVEBGREWRITEAOF两个命令都是通过创建子进程来执行的,但是出于服务器性能的考虑,这两个命令不能同时执行。

定时生成快照

除了通过命令生成RDB快照以外,Redis也支持定时生成快照的功能。通过在Redis配置文件中设置快照生成配置,服务器可以在运行过程中自动生成RDB快照。自动生成快照和BGSAVE命令执行的效果类似,也是通过创建子进程的方式生成RDB快照文件。

用户可以通过在配置文件中设置save选项的值来控制自动生成快照的频率。如果存在多个save选项配置,则任意一个配置满足条件都会触发生成RDB快照。

save 900 1
save 300 10
save 60 10000

上述配置的三个条件,只要满足下面任意一个条件,快照就会被创建:

  1. 服务器在900秒内,内存数据库发生了至少1次修改。
  2. 服务器在300秒内,内存数据库发生了至少10次修改。
  3. 服务器在60秒内,内存数据库发送了至少10000次修改。

载入RDB快照

当Redis服务器启动的时候如果发现存在RDB快照文件,则会进行RDB快照文件的载入。在RDB快照载入期间,Redis服务将会处于阻塞状态,直到快照载入完成。

1:M 12 Apr 2020 07:29:28.289 # Server initialized
1:M 12 Apr 2020 07:29:28.291 * DB loaded from disk: 0.001 seconds
...

RDB文件结构

通过RDB持久化创建的快照文件是一个由5部分组成的二进制文件。

  1. redis快照文件以REDIS字符串开头,占用5个字节(C语言中一个char类型占据1个字节)。用于在载入的时候检查文件是否是RDB快照文件。
  2. db_version的长度为4个字节,是一个字符串表示的整数,记录了RDB文件的版本号。
  3. databases是一个变长的字段,存放了Redis服务中的数据库数据。如果Redis数据库为空,则这个字段的长度为0。
  4. EOF是长度为1个字节的常量,用于标识RDB文件正文内容的结束。
  5. check_sum是一个长度为8个字节的无符号整数,保存了前四部分的校验和,用于在载入RDB文件的时候进行文件完整性检查。

关于RDB文件格式的详细细节可以参考《Redis设计与实现》一书,作者对RDB文件格式做了详尽的介绍。

优缺点

优点

  1. 生成RDB快照文件的过程是异步的,所以在持久化过程中对服务器性能影响小。
  2. RDB文件存储的是内存数据库的快照,采用紧凑的二进制文件存储。通过RDB文件进行数据库恢复的时候速度快。

缺点

  1. 由于RDB文件是异步进行备份的,所以存在数据安全性弱的弊端:当系统发生故障导致内存数据库数据丢失的时候,从RDB文件中只能恢复创建RDB快照那一刻的数据,在最近一次创建RDB快照那一刻到服务器宕机之间的数据将永久性的丢失了。数据恢复的完整程度依赖于RDB快照创建的频率。
  2. 由于RDB快照是将整个内存数据库备份下来,所以当内存数据库很大的时候创建RDB文件需要耗费更久的时间。

AOF持久化

鉴于RDB持久化方案存在的一些问题,Redis提供了另外一种持久化机制:AOF(Append Only File)持久化功能。

不同于RDB持久化方案通过创建快照文件来持久化数据,AOF持久化方案通过持久化发送到Redis服务器的写命令来实现持久化功能。AOF持久化机制会把所有引起内存数据库数据变化的写命令都保存下来,通过文件追加(Append)的方式保存到AOF文件中。在通过AOF文件进行数据恢复的时候我们可以通过重放AOF文件中的命令来恢复出Redis内存数据库的内容,这就是AOF机制能进行持久化的原理。

AOF持久化方案通过牺牲一定的性能来换取数据的安全性,以满足对数据安全性要求较高的场景。

 

创建AOF文件

当Redis服务器启用了AOF持久化选项以后,服务器会将接收到的写命令以追加的方式写入服务器的AOF缓冲区,然后由服务器按照不同的AOF持久化选项以不同的策略将缓冲区的命令写入AOF文件。

AOF持久化选项

以追加方式进行文件写入本质上是一种顺序访问磁盘的方式。对于磁盘这种存储介质来说,顺序访问比随机访问的性能会高很多,所以追加方式写入磁盘对服务的性能影响较小。但是这种性能的消耗在有些场景下可能是不能接受的,所以为了兼顾数据安全性和性能,Redis的AOF持久化方案提供了一些持久化选项。

Redis通过设置appendfsync选项来控制持久化的数据安全程度。该参数提供了三个可选的AOF持久化选项值:alwayseverysecno。分别对应不同的数据安全级别。

选项值 作用
always 每执行一次命令就进行AOF文件同步
everysec 每隔一秒进行一次AOF文件同步。由于同步AOF文件是阻塞操作,所以当前一次同步操作耗时超过1秒的时候,下一次同步操作将会到等上一次同步完成以后才进行,因此最差情况下会造成延迟2秒的同步
no Redis不主动进行AOF文件同步,而是交给操作系统定时进行文件同步

注意:如果appendfsync选项的值没有配置,则默认值为everysec

我们在第一节中讨论持久化面临的问题的时候,提到服务器能控制的持久化步骤为第1-4步。其中第4步通过fsync()命令将内核缓冲区中的数据刷新到磁盘中(Linux环境)。Linux环境下的Redis服务器就是通过该系统调用来实现appendfsync的不同持久化选项的。

由于每次进行fsync()系统调用相对比较耗时,所以AOF持久化方案提供了上述三种选项供开发者选择,以满足对数据安全性要求不同的使用场景。

如果appendfsync选项的值被配置为always,那么数据安全性最高,但是服务的性能会受到影响;如果选项值设置为no,那么数据安全性的保证相对较弱,但是服务器的性能有所提高;而everysec则是这两种场景的一个折中。

从AOF文件恢复

和RDB快照文件直接包含数据库状态不同,AOF文件包含了Redis服务器收到的所有写命令,所以当服务器从AOF文件恢复数据库的时候,需要对AOF文件中的所有命令按序进行重放来还原出数据库状态。

在Redis服务器通过AOF文件恢复数据库的时候,为AOF文件创建一个伪客户端,然后通过这个伪客户端来执行AOF文件中的命令,以此来重建数据库。

重写AOF文件

由于AOF文件恢复需要重放AOF文件中的所有命令,所以数据库的恢复时间和AOF文件的大小成正比。当AOF文件很大的时候恢复过程需要耗费很长一段时间才能完成,而RDB由于存储的是快照,所以没有这方面的困扰,不过RDB快照恢复的速度和数据库的大小正相关。

Redis为了解决AOF文件太大的情况,提供了AOF文件重写的功能。通过对AOF文件进行重写,将一些命令进行合并来达到缩减AOF文件的目的,最终实现减少AOF文件恢复时间的目的。

比如,通过对下面的这几个命令进行重写,产生一个重写后的命令来达到和重写前同样的效果:

# 重写前
PUSH list "A"
PUSH list "B"

# 重写后
PUSH list "A" "B"

Redis提供了BGREWRITEAOF命令进行AOF重写操作。BGREWRITEAOF命令是一个后台执行的命令,通过创建一个子进程来完成AOF文件重写工作。

重写过程

在AOF文件重写过程中Redis服务器还可以继续处理请求,所以AOF文件会继续追加命令,如果不对AOF重写和命令追加进行协调,那么将导致AOF文件数据不一致的情况。

为了解决这个问题,Redis服务器会在AOF文件重写开始以后创建一个AOF重写缓冲区。当服务器接收到写命令以后,会同步将这个命令写入AOF重写缓冲区和原先的AOF追加缓冲区。这保证了在AOF重写期间,Redis服务器可以继续进行AOF持久化,而且新接受到的命令也会被记录到AOF重写缓冲区中。

当处理AOF重写的子进程完成AOF重写工作以后,会通过信号机制通知父进程(服务器进程),父进程接收到信号以后会执行如下两步操作:

  1. 服务器进程会将AOF重写缓冲区中的命令写入重写后的新AOF文件中。
  2. 服务器会原子的将AOF文件修改为重写后的AOF文件,完成新旧AOF文件的替换。

优缺点

优点

  1. 数据的安全性更高,当服务crash以后丢失的数据更少。
  2. AOF文件的可读性更好。
  3. 通过append方式追加命令,访问磁盘的效率高。

缺点

  1. 由于AOF持久化会在一定程度上进行磁盘同步处理操作,这个过程是阻塞的(虽然很短),所以对服务器处理命令的性能会产生影响。
  2. AOF文件记录的是写命令,恢复的时候需要重放命令来得到内存数据库的状态,即使有AOF重写机制,恢复速度上和RDB相比也会有差距。

从RDB持久化转换成AOF持久化

在调整持久化方案的时候,Redis在不同版本有不同的操作方式。在Redis 2.2以后的版本中可以在不停机的情况下修改持久化方案,通过如下步骤进行操作:

  1. 创建最近的RDB文件的备份。
  2. 将备份保存在安全的位置。
  3. 发起如下命令:
    • 客户端执行 config set appendonly yes启用AOF持久化。
    • 客户端执行 config set save “”关闭RDB持久化。
  4. 确认数据库包含相同的keys。
  5. 确认write操作被正确追加到了AOF文件。
  6. 修改redis.conf文件,保证下次重启的时候也是正确的持久化配置。

同时使用AOF持久化和RDB持久化

在AOF和RDB两个持久化方案都启动的情况下,除了RDB的快照命令SAVE会阻塞进程导致其他命令都不能被执行之外,BGSAVEBGREWRITEAOF命令都是在子进程中异步执行的,不会影响到服务器进程的正常执行。不过考虑到持久化操作会占用磁盘IO资源,对机器的IO性能会产生影响,Redis对BGSAVEBGREWRITEAOF命令的执行做了限制:同一时刻服务中只能执行一个命令。

同样,在AOF和RDB这两个持久化方案都启用的情况下恢复数据的时候,由于AOF文件的数据安全性(完整性)更高,Redis在启动的时候会优先采用AOF文件恢复的方式重建数据库,只有当AOF功能关闭的情况下才会使用RDB进行数据重建。

总结

在本文中,我们介绍了Redis的两种持久化方案,分析了两种方案各自的实现原理和优缺点。