详解Redis持久化(RDB和AOF)
昂迪梵德 人气:0
# 详解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 命令请求的协议格式
![file](https://img2020.cnblogs.com/other/1973632/202004/1973632-20200402141451759-1510784666.jpg)
(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 文件重写缓存区中。
![file](https://img2020.cnblogs.com/other/1973632/202004/1973632-20200402141451978-255355315.jpg)
有了 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 文件中的所有命令读取完毕,并成功执行为止。这个过程完成之后,就可以将服务器的数据库状态还原为关闭之前的状态。
![file](https://img2020.cnblogs.com/other/1973632/202004/1973632-20200402141452453-719433567.jpg)
如果在 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 文件结构:
![img](https://img2020.cnblogs.com/other/1973632/202004/1973632-20200402141452710-518784256.jpg)
在 RDB 文件结构中,通常使用大写字母表示常量,使用小写字母表示变量和数据。
● REDIS 常量:该常量位于 RDB 文件的头部,它保存着「REDIS」5 个字符,它的长度是 5 字节。在 Redis 服务器启动加载文件时,程序会根据这 5 个字符判断加载的文件是不是 RDB 文件。
● db_version 常量:该常量用于记录 RDB 文件的版本号,它的值是一个用字符串表示的整数,占 4 字节,注意区分它不是 Redis 的版本号。
● databases 数据:它包含 0 个或多个数据库,以及各个数据库中的键值对数据。
如果它包含 0 个数据库,也就是服务器的数据库状态为空,那么 databases 也是空的,其长度为 0 字节;如果它包含多个数据库,也就是服务器的数据库状态不为空,那么 databases 不为空,根据它所保存的键值对的数量、类型和内容不同,其长度也是不一样的。
如果 databases 不为空,则 RDB 文件结构如图
![img](https://img2020.cnblogs.com/other/1973632/202004/1973632-20200402141452894-1576695532.jpg)
其中,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 中,默认开启压缩。配置如下:
![img](https://img2020.cnblogs.com/other/1973632/202004/1973632-20200402141453054-1695705789.jpg)
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 文件。
加载全部内容