详解Redis持久化(RDB和AOF)-阿里云开发者社区

开发者社区> 数据库> 正文

详解Redis持久化(RDB和AOF)

简介:

详解Redis持久化(RDB和AOF)
什么是Redis持久化?
Redis读写速度快、性能优越是因为它将所有数据存在了内存中,然而,当Redis进程退出或重启后,所有数据就会丢失。所以我们希望Redis能保存数据到硬盘中,在Redis服务重启之后,原来的数据能够恢复,这个过程就叫持久化。

Redis持久化的两种方式?RDB和AOF
AOF:会将每次执行的命令及时保存到硬盘中,实时性更好,丢失的数据更少

RDB:会根据指定的规则定时将内存中的数据保存到硬盘中。

通常两种方式结合使用,下面详细介绍RDB和AOF

AOF
Append Only File,AOF会保存服务器执行的所有写操作到日志文件中,在服务重启以后,会执行这些命令来恢复数据。

日志文件默认为appendonly.aof,Redis以Redis协议格式将命令保存至aof日志文件末尾,aof文件还会被重写,使aof文件的体积不会大于保存数据集状态所需要的实际大小

默认情况下,aof没有被开启。需要在redis.conf开启

appendonly yes
日志文件名

appendfilename "appendonly.aof"
日志文件所在目录(RDB日志文件也是在这里)

dir ./
fsync持久化策略

appendfsync everysec
always:命令写入aof_buf后立即调用系统fsync操作同步到AOF文件,fsync完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。

no:命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。

everysec:命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置。

其余参数:

● no-appendfsync-on-rewrite no:在重写 AOF 文件的过程中,是否禁止 fsync。如果这个参数值设置为 yes(开启),则可以减轻重写 AOF 文件时 CPU 和硬盘的负载,但同时可能会丢失重写 AOF 文件过程中的数据;需要在负载与安全性之间进行平衡。

● auto-aof-rewrite-percentage 100:指定 Redis 重写 AOF 文件的条件,默认为 100,它会对比上次生成的 AOF 文件大小。如果当前 AOF 文件的增长量大于上次 AOF 文件的 100%,就会触发重写操作;如果将该选项设置为 0,则不会触发重写操作。

● auto-aof-rewrite-min-size 64mb:指定触发重写操作的 AOF 文件的大小,默认为 64MB。如果当前 AOF 文件的大小低于该值,此时就算当前文件的增量比例达到了 auto-aof-rewrite-percentage 选项所设置的条件,也不会触发重写操作。换句话说,只有同时满足以上这两个选项所设置的条件,才会触发重写操作。

● auto-load-truncated yes:当 AOF 文件结尾遭到损坏时,Redis 在启动时是否仍加载 AOF 文件。

AOF持久化的实现
(1)命令追加(append):Redis 服务器每执行一条写命令,这条写命令都会被追加到缓存区 aof_buf 中。(避免每次执行的命令都直接写入硬盘中,会导致硬盘 I/O 的负载过大,使得性能下降。)

命令追加的格式使用 Redis 命令请求的协议格式

(2)AOF 持久化文件写入(write)和文件同步(sync):根据 appendfsync 参数设置的不同的同步策略,将缓存区 aof_buf 中的数据内容同步到硬盘中。

先了解操作系统的 write 和 fsync 函数。为了提高文件的写入效率,当用户调用 write 函数将数据写入文件中时,操作系统会将这些数据暂存到一个内存缓存区中,当这个缓存区被填满或者超过了指定时限后,才会将缓存区中的数据写入硬盘中,这样做既提高了效率,又保证了安全性。

Redis 的服务器进程是一个事件循环(loop),这个事件循环中的文件事件负责接收客户端的命令请求,处理之后,向客户端发送命令回复;而其中的时间事件则负责执行像 serverCron 函数这样需要定时运行的函数。

服务器在处理文件事件时,可能会执行客户端发送过来的写命令,使得一些命令被追加到缓存区 aof_buf 中。因此,在服务器每次结束一个事件循环之前,都会调用 flushAppendOnlyFile 函数,来决定是否将缓存区 aof_buf 中的数据写入和保存到 AOF 文件中。

flushAppendOnlyFile 函数的运行与服务器配置的 appendfsync 参数有关。

AOF文件的重写
AOF文件定期重写,以达到压缩的目的。

AOF文件过于庞大,会影响Redis的写入速度,在执行数据恢复时,也非常满。AOF文件重写就是解决这个问题。

AOF 文件重写就是把 Redis 进程内的数据转化为写命令,然后同步到新的 AOF 文件中。在重写的过程中,Redis 服务器会创建一个新的 AOF 文件来替代现有的 AOF 文件,新、旧两个 AOF 文件所保存的数据库状态相同,但是新的 AOF 文件不会包含冗余命令。

AOF 文件重写并不会对旧的 AOF 文件进行读取、写入操作,这个功能是通过读取服务器当前的数据库状态来实现的。

举例说明:

​ 比如Redis使用五条rpush命令分别插入五种颜色

rpush color 〝yellow〝
rpush color 〝green〝
...
​ AOF重写后

RPUSH color 〝yellow〝 〝green〝 〝black〝 〝pink〝〝white〝
所以AOF重写的原理:

● AOF 文件重写功能会丢弃过期的数据,也就是过期的数据不会被写入 AOF 文件中。

● AOF 文件重写功能会丢弃无效的命令,无效的命令将不会被写入 AOF 文件中。无效命令包括重复设置某个键值对时的命令、删除某些数据时的命令等。

● AOF 文件重写功能可以将多条命令合并为一条命令,然后写入 AOF 文件中。

怎么触发AOF重写?

● 手动触发:执行 BGREWRITEAOF 命令触发 AOF 文件重写。该命令与 BGSAVE 命令相似,都是启动(fork)子进程完成具体的工作,且都在启动时阻塞。

● 自动触发:自动触发 AOF 文件重写是通过设置 Redis 配置文件中 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size 参数的值,以及 aof_current_size 和 aof_base_size 状态来确定何时触发的。

auto-aof-rewrite-percentage 参数是在执行 AOF 文件重写时,当前 AOF 文件的大小(aof_current_size)和上一次 AOF 文件重写时的大小(aof_base_size)的比值,默认为 100。

auto-aof-rewrite-min-size 参数设置了执行 AOF 文件重写时的最小体积,默认为 64MB。

AOF文件的后台重写:
AOF重写执行大量的写入操作,就会使得这个函数的线程被长时间阻塞。Redis 服务器使用单线程来处理命令请求。如果让服务器直接调用 aof_rewrite 重写函数,那么在 AOF 文件重写期间,服务器将不能继续执行其他命令,就会一直处于阻塞状态。

Redis 将 AOF 文件重写程序放到了一个子进程中执行,这样做的好处是:

● 子进程在执行 AOF 文件重写的过程中,Redis 服务器进程可以继续处理新的命令请求。

● 子进程带有服务器进程的数据副本,使用子进程可以在使用锁的情况下,保证数据的安全性。

使用子进程会导致数据库状态不一致,原因是:当子进程进行 AOF 文件重写的时候,Redis 服务器可以继续执行来自客户端的命令请求,就会有新的命令对现有数据库状态进行修改,进而使得服务器当前的数据库状态与重写的 AOF 文件所保存的数据库状态不一致。

为了解决使用子进程导致数据库状态不一致的问题,Redis 服务器设置了一个 AOF 文件重写缓存区。这个 AOF 文件重写缓存区在服务器创建子进程之后开始使用,可以利用它来解决数据库状态不一致的问题。当 Redis 服务器成功执行完一条写命令后,它会同时将这条写命令发送给 AOF 文件缓存区(aof_buf)和 AOF 文件重写缓存区。

子进程在执行 AOF 文件重写的过程中,服务器进程的执行过程如下:

(1)服务器接收到来自客户端的命令请求,并成功执行。

(2)服务器将执行后的写命令转化为对应的协议格式,然后追加到 AOF 文件缓存区(aof_buf)中。

(3)服务器再将执行后的写命令追加到 AOF 文件重写缓存区中。

有了 AOF 文件重写缓存区,就可以保证数据库状态的一致性。AOF 文件缓存区的内容会被定期写入和同步到 AOF 文件中,AOF 文件的写入和同步不会因为 AOF 文件重写缓存区的引入而受到影响。当服务器创建子进程之后,服务器执行的所有写命令都会同时被追加到 AOF 文件缓存区和 AOF 文件重写缓存区中。

如果子进程完成了 AOF 文件重写的工作,它就会发送一个完成信号给父进程。当父进程接收到这个信号后,就会调用信号处理函数,继续执行以下工作:

(1)将 AOF 文件重写缓存区中的所有内容写入新的 AOF 文件中。新的 AOF 文件所保存的数据库状态与服务器当前的数据库状态保持一致。

(2)修改新的 AOF 文件的文件名,新生成的 AOF 文件将会覆盖现有(旧)的 AOF 文件,完成新、旧两个文件的互换。

在完成上述两个步骤之后,就完成了一次 AOF 文件后台重写工作。

在整个 AOF 文件后台重写的过程中,只有在信号处理函数执行的过程中,服务器进程才会被阻塞,在其他时候不存在阻塞情况。

AOF文件恢复数据的过程
(1)创建一个伪客户端,用于执行 AOF 文件中的写命令。这个伪客户端是一个不带网络连接的客户端。因为只能在客户端的上下文中才能执行 Redis 的命令,而在 AOF 文件中包含了 Redis 服务器启动加载 AOF 文件时所使用的所有命令,而不是网络连接,所以服务器创建了一个不带网络连接的伪客户端来执行 AOF 文件中的写命令。

(2)读取 AOF 文件中的数据,分析并提取出 AOF 文件所保存的一条写命令。

(3)使用伪客户端执行被读取出的写命令。

(4)重复执行步骤(2)和(3),直到将 AOF 文件中的所有命令读取完毕,并成功执行为止。这个过程完成之后,就可以将服务器的数据库状态还原为关闭之前的状态。

如果在 Redis 服务器启动加载 AOF 文件时,发现 AOF 文件被损坏了,那么服务器会拒绝加载这个 AOF 文件,以此来确保数据的一致性不被破坏。而 AOF 文件被损坏的原因可能是程序正在对 AOF 文件进行写入与同步时,服务器出现停机故障。如果 AOF 文件被损坏了,则可以通过以下方法来修复。

● 及时备份现有 AOF 文件。

● 利用 Redis 自带的 redis-check-aof 程序,对原来的 AOF 文件进行修复,命令如下:

● 使用 diff-u 来对比原始 AOF 文件和修复后的 AOF 文件,找出这两个文件的不同之处。

● 修复 AOF 文件之后,重启 Redis 服务器重新加载,进行数据恢复。

AOF持久化的优劣
● 使用 AOF 持久化会让 Redis 持久化更长:通过设置不同的 fsync 策略来达到更长的持久化。具体有 3 种策略。

● 兼容性比较好:AOF 文件是一个日志文件,它的作用是记录服务器执行的所有写命令。当文件因为某条写命令写入失败时,可以使用 redis-check-aof 进行修复,然后继续使用。

● 支持后台重写:当 AOF 文件的体积过大时,在后台可以自动地对 AOF 文件进行重写,因此数据库当前状态的所有命令集合都会被重写到 AOF 文件中。重写完成后,Redis 就会切换到新的 AOF 文件,继续执行写命令的追加操作。

● AOF 文件易于读取和加载:AOF 文件保存了对数据库的所有写命令,这些写命令采用 Redis 协议格式追加到 AOF 文件中,因此非常容易读取和加载。

AOF 持久化具有以下缺点:

● AOF 文件的体积会随着时间的推移逐渐变大,导致在加载时速度会比较慢,进而影响数据库状态的恢复速度,性能快速下降。

● 根据所使用的 fsync 策略,使用 AOF 文件恢复数据的速度可能会慢于使用 RDB 文件恢复数据的速度。

● 因为 AOF 文件的个别命令,可能会导致在加载时失败,从而无法进行数据恢复。

RDB
RDB 持久化生成的 RDB 文件是一个经过压缩的二进制文件,也可以称之为快照文件,通过该文件可以还原生成 RDB 文件时的数据库状态

在指定的时间间隔内,Redis 会自动将内存中的所有数据生成一份副本并存储在硬盘上,这个过程就是「快照」。

快照怎么生成
● 根据 Redis 配置文件 redis.conf 中的配置自动进行快照

save 900 1
save 300 10
save 60 10000
save m n //时间 m 和被修改的键的个数 n
当在时间 m 内被修改的键的个数大于 n 时,就会触发 BGSAVE 命令,服务器就会自动执行快照操作。

Redis 的 save m n 命令是通过 serverCron 函数、dirty 计数器及 lastsave 时间戳来实现的。

serverCron 函数:这是 Redis 服务器的周期性操作函数,默认每隔 100 毫秒执行一次,它主要的作用是维护服务器的状态。其中一项工作就是判断 save m n 配置的条件是否满足,如果满足就会触发执行 BGSAVE 命令。

dirty 计数器:这是 Redis 服务器维持的一种状态,它主要用于记录上一次执行 SAVE 或 BGSAVE 命令后,服务器进行了多少次状态修改(执行添加、删除、修改等操作);当 SAVE 或 BGSAVE 命令执行完成后,服务器会将 dirty 重新设置为 0。dirty 计数器记录的是服务器进行了多少次状态修改,而不是客户端执行了多少次修改数据的命令。

lastsave 时间戳:主要用于记录服务器上一次成功执行 SAVE 或 BGSAVE 命令的时间,它是 Redis 服务器维持的一种状态。

dirty 计数器和 lastsave 时间戳属性都保存在服务器状态的 redisServer 结构中。

save m n 命令的实现原理:服务器每隔 100 毫秒执行一次 serverCron 函数;serverCron 函数会遍历 save m n 配置的保存条件,判断是否满足。如果有一个条件满足,就会触发执行 BGSAVE 命令,进行快照保存。

对于每个 save m n 条件,只有以下两个条件同时满足才算满足:

➢ 当前服务器时间减去 lastsave 时间戳大于 m。

➢ 当前 dirty 计数器的个数大于等于 n。

● 用户在客户端执行 SAVE 或 BGSAVE 命令时会触发快照(手动触发)。

● 如果用户定义了自动快照条件,则执行 FLUSHALL 命令也会触发快照。

当执行 FLUSHALL 命令时,会清空数据库中的所有数据。如果用户定义了自动快照条件,则在使用 FLUSHALL 命令清空数据库的过程中,就会触发服务器执行一次快照。

● 如果用户为 Redis 设置了主从复制模式,从节点执行全量复制操作,则主节点会执行 BGSAVE 命令,将生产的 RDB 文件发送给从节点完成快照操作。

快照的实现过程
(1)Redis 调用执行 fork 函数复制一份当前进程(父进程)的副本(子进程),也就是同时拥有父进程和子进程。

(2)父进程与子进程各自分工,父进程继续处理来自客户端的命令请求,而子进程则将内存中的数据写到硬盘上的一个临时 RDB 文件中。

(3)当子进程把所有数据写完后,也就表示快照生成完毕,此时旧的 RDB 文件将会被这个临时 RDB 文件替换,这个旧的 RDB 文件也会被删除。这个过程就是一次快照的实现过程。

当 Redis 调用执行 fork 函数时,操作系统会使用写时复制策略。也就是在执行 fork 函数的过程中,父、子进程共享同一内存数据,当父进程要修改某个数据时(执行一条写命令),操作系统会将这个共享内存数据另外复制一份给子进程使用,以此来保证子进程的正确运行。因此,新的 RDB 文件存储的是执行 fork 函数过程中的内存数据。

写时复制策略也保证了在执行 fork 函数的过程中生成的两份内存副本在内存中的占用量不会增加一倍。但是,在进行快照的过程中,如果写操作比较多,就会造成 fork 函数执行前后数据差异较大,此时会使得内存使用量变大。因为内存中不仅保存了当前数据库数据,还会保存 fork 过程中的内存数据。

在进行快照生成的过程中,Redis 不会修改 RDB 文件。只有当快照生成后,旧的 RDB 文件才会被临时 RDB 文件替换,同时旧的 RDB 文件会被删除。在整个过程中,RDB 文件是完整的,因此我们可以使用 RDB 文件来实现 Redis 数据库的备份。

RDB 文件
默认情况下,Redis 将数据库快照保存在名为 dump.rdb 的文件中。这个文件可以修改,即AOF的dir和dbfilename 属性

RDB 文件结构:

在 RDB 文件结构中,通常使用大写字母表示常量,使用小写字母表示变量和数据。

● REDIS 常量:该常量位于 RDB 文件的头部,它保存着「REDIS」5 个字符,它的长度是 5 字节。在 Redis 服务器启动加载文件时,程序会根据这 5 个字符判断加载的文件是不是 RDB 文件。

● db_version 常量:该常量用于记录 RDB 文件的版本号,它的值是一个用字符串表示的整数,占 4 字节,注意区分它不是 Redis 的版本号。

● databases 数据:它包含 0 个或多个数据库,以及各个数据库中的键值对数据。

如果它包含 0 个数据库,也就是服务器的数据库状态为空,那么 databases 也是空的,其长度为 0 字节;如果它包含多个数据库,也就是服务器的数据库状态不为空,那么 databases 不为空,根据它所保存的键值对的数量、类型和内容不同,其长度也是不一样的。

如果 databases 不为空,则 RDB 文件结构如图

其中,SELECTDB 是一个常量,表示其后的数据库编号,这里的 0 和 1 是数据库编号。

pairs 数据:它存储了具体的键值对信息,包括键(key)、值(value)、数据类型、内部编码、过期信息、压缩信息等。

SELECT 0 pairs 表示 0 号数据库;SELECT 1 pairs 表示 1 号数据库。当数据库中有键值对时,RDB 文件才会记录该数据库的信息;而如果数据库中没有键值对,这一部分就会被 RDB 文件省略。

● EOF 常量:该常量是一个结束标志,它标志着 RDB 文件的正文内容结束,其长度为 1 字节。在加载 RDB 文件时,如果遇到 EOF 常量,则表示数据库中的所有键值对都已经加载完毕。

● check_sum 变量:该变量用于保存一个校验和,这个校验和是通过对 REDIS、db_version、databases、EOF 4 部分的内容进行计算得出的,是一个无符号整数,其长度为 8 字节。

当服务器加载 RDB 文件时,会将 check_sum 变量中保存的校验和与加载数据时所计算出来的校验和进行比对,从而判断加载的 RDB 文件是否被损坏,或者是否有错误。

RDB文件压缩
在默认情况下,Redis 服务器会自动对 RDB 文件进行压缩。在 Redis 配置文件 redis.conf 中,默认开启压缩。配置如下:

Redis 采用 LZF 算法进行 RDB 文件压缩。在压缩 RDB 文件时,不要误认为是压缩整个 RDB 文件。实际上,对 RDB 文件的压缩只是针对数据库中的字符串进行的,并且只有当字符串达到一定长度(20 字节)时才会进行压缩。

RDB文件的创建
SAVE 命令: 会阻塞 Redis 服务器进程,此时 Redis 服务器将不能继续执行其他命令请求,直到 RDB 文件创建完毕为止。

BGSAVE 命令: 会派生出一个子进程,交由子进程将内存中的数据保存到硬盘中,创建 RDB 文件;而 BGSAVE 命令的父进程可以继续处理来自客户端的命令请求。

执行 BGSAVE 命令会返回 Background saving started 信息,但我们并不能确定 BGSAVE 命令是否已经成功执行,此时可以使用 LASTSAVE 命令来查看相关信息。

执行 LASTSAVE 命令返回一个 UNIX 格式的时间戳,表示最近一次 Redis 成功将数据保存到硬盘中的时间。

RDB文件的加载
RDB 文件只有在启动服务器的时候才会被加载。当启动服务器时,它会检查 RDB 文件是否存在,如果存在,就会自动加载 RDB 文件。除此之外,RDB 文件不会被加载,因为 Redis 中没有提供用于加载 RDB 文件的命令。

那么先加载AOF文件还是RDB文件呢?

● 如果在 Redis 配置文件中开启了 AOF 持久化(appendonly yes),那么在启动服务器的时候会优先加载 AOF 文件来还原数据库状态。

● 如果在 Redis 配置文件中关闭了 AOF 持久化(appendonly no),那么在启动服务器的时候会优先加载 RDB 文件来还原数据库状态。

RDB文件的配置
除了save m n、 dbfilename dump.rdb、 dir./ 还有

● stop-writes-on-bgsave-error yes:当执行 BGSAVE 命令出现错误时,Redis 是否终止执行写命令。参数的值默认被设置为 yes,表示当硬盘出现问题时,服务器可以及时发现,及时避免大量数据丢失;当设置为 no 时,就算执行 BGSAVE 命令发生错误,服务器也会继续执行写命令;当对 Redis 服务器的系统设置了监控时,建议将该参数值设置为 no。

● rdbcompression yes:是否开启 RDB 压缩文件,默认为 yes 表示开启,不开启则设置为 no。

● rdbchecksum yes:是否开启 RDB 文件的校验,在服务器进行 RDB 文件的写入与读取时会用到它。默认设置为 yes。如果将它设置为 no,则在服务器对 RDB 文件进行写入与读取时,可以提升性能,但是无法确定 RDB 文件是否已经被损坏。

RDB 持久化的优劣
● RDB 文件是一个经过压缩的二进制文件,文件紧凑,体积较小,非常适用于进行数据库数据备份。

● RDB 持久化适用于灾难恢复,而且恢复数据时的速度要快于 AOF 持久化。

● Redis 采用 RDB 持久化可以很大程度地提升性能。父进程在保存 RDB 文件时会启动一个子进程,将所有与保存相关的功能交由子进程处理,而父进程可以继续处理其他相关的操作。

RDB 持久化具有以下缺点:

● 在服务器出现故障时,如果没有触发 RDB 快照执行,那么它可能会丢失大量数据。RDB 快照的持久化方式决定了必然做不到实时持久化,会存在大量数据丢失。

● 当数据量非常庞大时,在保存 RDB 文件的时候,服务器会启动一个子进程来完成相关的保存操作。这项操作比较耗时,将会占用太多 CPU 时间,从而影响服务器的性能。

● RDB 文件存在兼容性问题,老版本的 Redis 不支持新版本的 RDB 文件。

原文地址https://www.cnblogs.com/undefined22/p/12619546.html

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
数据库
使用钉钉扫一扫加入圈子
+ 订阅

分享数据库前沿,解构实战干货,推动数据库技术变革

其他文章