Redis学习笔记(一):Redis持久化——RDB与AOF

引子

前两天晚上下班的时候,和朋友走在路上聊起了Redis的主从复制与哨兵模式。突然觉得自己对这一块的知识还没有一个比较系统化的了解。所以,现在就开始仔细学习一下相关的知识,整理一下Redis相关的知识点,这里做个记录,同时也希望能帮助到大家。

系统环境

CentOS 7.6.1810       Redis 5.0.5

Redis安装

这里,我将简单记录下Redis安装的一些关键命令,如果还有不清楚地地方,请大家自行谷歌~

# 编译Redis
cd redis-5.0.5
make
# 编译安装
cd src
make install PREFIX=/home/tom/data/redis-5.0.5
# 将配置文件移动过去
mv redis.conf ../redis-5.0.5/redis.conf
# 删除原来的编译包(可选)
rm -rf redis-5.0

然后,修改部分配置项:

# 支持外部访问
bind 0.0.0.0
# 后台运行
daemonize yes
# 日志路径
logfile "logs/redis-6397.log"
# 密码
requirepass 123456

保存配置,启动Redis:

cd redis-5.0.5
mkdir logs
./bin/redis-server redis.conf

至此,Redis安装,并配置启动完毕。

Redis持久化概述

我们都知道,Redis是一款内存数据库,不管是系统宕机,还是Redis进程退出,内存中的数据都会丢失。如果说,我们仅仅将Redis用作项目的缓存,而不会涉及到任务业务数据的存储时,我们可能并不需要将Redis中的数据持久化。但是,如果我们是将业务中的热点数据放到Redis,这时候,数据丢失就是大问题了。为了解决这个问题,Redis为我们提供了两种不同的持久化选项:快照(RDB)和追加(AOF)。下面我参考着官方文档,加上我的理解,详细的介绍下这两种持久化方式。

快照(RDB)

默认情况下,Redis将数据集的快照写入一个名叫dump.rdb 的文件中,保存在磁盘上。这个配置在redis.conf配置文件中:

dbfilename dump.rdb

当然,这个文件的名称与路径,我们都是可以自定义的。修改配置中的路径即可。

下面,介绍下开启RDB快照持久化的三种方式。

一、SAVE命令

SAVE命令将会执行一次 同步 的数据集持久化操作,将当前时间点Redis实例内所有的数据都写入到一个RDB格式的文件中。因为这是一个同步的操作,所以,在数据集同步期间,服务端将会阻塞其他客户端的读、写请求,直至服务端数据集备份完毕。

由于SAVE命令每次都会执行一次全备份,如果数据集比较大的情况下,这个备份操作将会使得服务端在相当长的一段时间内无法响应客户端的请求。所以,一般情况下,不要在生产环境直接使用SAVE命令!

同时,在SAVE命令的官方文档中,提到这么一点:如果出现了系统阻止Redis创建子进程的问题(例如fork(2)系统调用中的错误),SAVE命令可能是执行最新数据转储的最后手段。这里fork(2)系统调用错误,有兴趣的朋友可以自行去了解~

另外,补充一点,在SAVE命令执行完毕后,如果执行成功,该命令将会返回“OK”这个字符串。

二、BGSAVE命令

与上面提到的SAVE命令不同,BGSAVE将执行一次 异步 持久化操作。当客户端执行BGSAVE命令时,服务端将会fork一个子进程去进行数据集持久化,同时,父进程将继续为客户端服务。当数据持久化完毕时,子进程会自己退出。

需要注意的是,这个fork操作,仍然是一个同步操作,如果数据集比较大,那么fork进程这个操作过程可能耗时也比较大,在这段时间内,服务端仍然会阻塞客户端的读、写请求。数据集比较大时,可能导致Redis在毫秒级的时间内无法响应客户端的请求。如果数据集特别大同时CPU的性能又不好的情况下,这个情况可能会持续1秒。当然,一般情况下,fork()是很快的。

当我看到上面这一段的官方文档的时候,我非常疑惑:fork()操作的耗时,为什么和Redis实例的数据集的大小有关系?这个问题,在问题解决中我会详细谈到。

关于BGSAVE,还需要补充一些点。对于SAVE命令,上文我们提到,它会等到持久化操作执行完毕后,才会返回“OK”字符串标识。而BGSAVE不同,它会立即返回“OK”标识,无论持久化操作实际成功与否。我们可以通过 LASTSAVE 命令,获取上一次成功了的持久化操作完后时的时间戳,以此来判断我们上一次BGSAVE操作是否成功完成。

三、服务端自动保存

除了上面两种通过命令手动持久化的方式外,我们还可以通过在Redis.conf中配置策略,让服务端在指定条件下自动开启持久化备份操作。Redis 5.0.5中默认的配置如下:

save 900 1
save 300 10
save 60 10000

该如何理解呢?比如,对于【save 900 1】这个配置项来说,其意义是:每900秒,如果在这段时间内,至少有1个key-value键值对“被操作”过,那么就执行一次持久化操作。这里,我所说的“被操作”,包含以下几种情况:

  • 新增一个key-value键值对
  • 对已经存在的一个key,触发一次 SET 操作,不管SET的该key的新值和之前的值是否相同,都会被记录为“被操作”了一次
  • key过期,或者被删除
  • 可能还有一些我没发现的情况,如果有知道的朋友,可以留言指出。

通过配置开启持久化的这种方式,其工作方式和BGSAVE类似,服务端也会先fork()出一个子线进程,然后由子进程去进行持久化操作。

如果我们注释掉所有的配置,或者将配置改为 【save ""】,那么就相当于我们停用了 通过配置去持久化 这项功能。

其他要点

工作方式

针对通过 BGSAVE 和 配置 这两种方式进行的持久化操作,服务器执行以下操作:

  • Redis服务端fork()出一个子进程
  • 子进程将数据集写入到一个临时的RDB文件中
  • 子进程写入完毕,再使用临时文件替换掉原来的dump.rdb文件,然后子进程自己退出

而对于 SAVE 命令,除了第一步fork()子进程,后两步操作一样。

也就是说,无论何时,复制RDB文件都是安全的。 RDB 文件一旦被创建, 就不会进行任何修改。 当服务器要创建一个新的 RDB 文件时, 它先将文件的内容保存在一个临时文件里面, 当临时文件写入完毕时, 程序才使用 rename(2) 原子地用临时文件替换原来的 RDB 文件。

后台持久化失败策略

从redis.conf中作者的注释可以得到,在默认情况下,如果我们通过配置文件,配置了至少一种持久化策略,当后台持久化操作执行失败时,Redis将停止接受写入操作!这样做主要是为了让客户端端意识到,数据不能正确持久化到磁盘,否则可能出现灾难性的后果。当后台持久化进程又重新能正常工作时,Redis将会自动开始接受新的写入操作。当然,我们可以通过配置,停用这种机制,这样,即使后台持久化失败,Redis也将继续接受新的写入请求。默认该策略开启:

stop-writes-on-bgsave-error yes

String对象压缩

此外,RDB文件还支持“字符串”压缩。该配置项默认开启:

rdbcompression yes

该配置项的意义是,在转储 .rdb 文件的时候,对可压缩的对象使用LZF算法对String对象进行压缩。Redis作者也建议将该配置打开。

Checksum标记校验

从Redis 5.0开始,在RDB文件的末尾,将会防止一个CRC64的Checksum标记,这个主要是为了检查文件是否被损坏。当然,这个操作将会造成在持久化或恢复的时候有更多的性能损耗(大约10%)。该功能默认打开:

rdbchecksum yes

当然,我们也可以选择将该功能关闭。当我们关闭时,在持久化的时候,checksum位将会变为一个“零值”,这样在从RDB文件恢复的时候,将会跳过Checksum校验。

追加(Append-only file,AOF)

上面我说了将数据集快照写入RDB文件的持久化方式,这种方式可能存在数据丢失的情况。比如,我们配置了 【save 30 1】,那就可能出现几种极端,在某一次快照保存完毕后,下一次同步正准备开始时,Redis服务突然被停掉了,那么,这30秒内的发生改变的数据就会丢失。

从 1.1 版本开始,Redis新增了一种更可靠的持久化方式:AOF。当我们开启AOF持久化的时候,每当Redis执行一次数据集更改操作的命令,这些命令将会 异步 追加到AOF文件的末尾。当Redis重启时,这些将会被执行,重建之前的数据集。这样,即使出现系统宕机,或者Redis服务挂掉等问题,丢失的只是最近一次写入或者最近一秒(不同策略)的数据。

默认情况下,AOF模式是关闭的,因为RDB快照已经能够满足绝大多数应用的需求。我们可以通过配置文件,开启AOF持久化:

# 是否打开AOF模式,默认是 no,关闭
appendonly no
# .aof文件的路径、文件名
appendfilename "appendonly.aof"

下面,我们介绍下AOF模式的三种持久化策略:

# appendfsync always
  appendfsync everysec
# appendfsync no

一、always

每一次写入操作的命令,都会追加到AOF文件的末尾。这个写入操作是通过fsync()函数进行的,这是一个同步操作。

这种策略是最安全的,但是每一次追加操作都会存在fsync() IO操作,虽然安全,但是很费时间。

二、everysec

默认的策略,每一秒将前一秒新增的数据追加到AOF文件的末尾。这个操作也是通过fsync()函数进行的。

极端情况下,可能丢失1秒的数据。这种策略是在 安全性 和 效率之间的一个折中方案。

三、no

当我们使用 no 这种策略时,将不会调用 fsync()函数,而是由  操作系统决定何时 flush数据缓冲区。这种策略更快,但相对而言,可能丢失的数据也就变成了不确定。在Redis.conf文件中,作者在注释中写到:

if you can live with the idea of some data loss consider the default persistence mode that's snapshotting。

如果我们能接受一定数据的丢失,我们应该考虑 快照(RDB)持久化的方式。

日志重写

因为AOF总是不断将新的命令追加到AOF文件的末尾,随着写操作的越来越多,AOF文件也会越来越大。比如,我们对一个key执行100次 INCR 命令,AOF文件中实际记录了每一次执行的命令,但是实际上,对我们来说,有用的是最后一次记录的值。

为了解决这个问题,Redis支持在后端重建AOF文件,而不会中断接受客户端的请求。

日志重写原理

  1. Redis会fork()出一个新的AOF重写进程,这意味着,内存中Redis已有的数据会被复制到新的进程;
  2. 子进程将内存中的已有的Redis数据,写入到一个新的AOF文件中;
  3. 此时,对于新的数据更改,父进程将它们累积到内存缓存区中,同时,这些改动也会被追加到旧的 AOF 文件的末尾。这样即使在重写的中途发生宕机,现有的 AOF 文件也还是安全的。
  4. 当子进程完成日志重写操作后,父进程会获得一个信号,然后会将缓冲区中的更改附加到新生成的AOF文件的末尾;
  5. 最后, Redis 原子地用新AOF文件替换掉旧的AOF文件,并且之后所有新的命令都会直接追加到新 AOF 文件的末尾。

开启日志重写的两种方式:

一、BGREWRITEAOF命令:

当客户端手动执行 BGREWRITEAOF 命令时,Redis 将会开启一个AOF重写进程,重写一个 包含重建当前内存中数据集所需的最短命令序列 的临时文件。如果这次重写操作执行失败,也不会有数据丢失,原始的AOF文件不会受影响。整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。

需要注意的是,在默认情况下,只有当后台没有进程正在执行持久化操作时,AOF重写操作才会被触发。特别要注意:

  • 如果一个Redis子进程正在后台进行快照持久化操作,那么AOF重写操作就会等待,直到子进程将RDB文件写入完毕并退出后,AOF重写操作才会继续。在这种情况下,BGREWRIITEAOF 命令将会立即返回“OK”提醒码,同时也会返回相关信息。
  • 如果一次AOF重写正在进行中,此时又执行了BGREWRITEAOF 命令,那么该命令将会返回一个错误,并且,BGREWRITEAOF 命令也不会触发任何AOF重写操作。

这个功能,我也可以通过配置修改其策略:

no-appendfsync-on-rewrite no

默认情况下,该配置项的值为“no”。这个配置项的名称总是给人一种错觉,如果命名成【on-rewrite-appendfsync-no】这样,我相信大家一下子就明白了。这配置项什么意思呢?当我们将该配置项置为 yes 时,如果存在其他子进程在进行持久化操作时,上文提到的AOF策略就相当于被置为了 “appendfsync no”,此时,主进程接受到操作将会写入到AOF缓冲区,而不是直接写入AOF文件中,而后台的持久化操作继续进行IO操作,写入文件;当我们将该配置项置为 no 时,如果存在其他子进程在进行持久化操作,该配置项就会阻止Redis调用 fsync()函数,AOF重写操作就会等待,或者直接被拒绝。

二、服务端自动重写

从Redis 2.4版本开始,Redis支持当AOF文件 大小增量达到一定的百分比时,自动调用BGREWRITEAOF 命令进行AOF文件重写。下面是其默认配置:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

其工作机制是,在最近一次AOF重写后,Redis会记住AOF文件的大小(如果重启后没有发生AOF重写,则使用启动时AOF文件的大小。),并以此大小作为基准,和当前的文件大小作比较,如果当前的文件比基准大小 大于 了指定的百分比,则会触发重写。此外,我们还需要为要重写的AOF文件指定最小大小,这样,即使达到了百分比增长,但AOF仍然小于指定最小大小时,则不会触发AOF重写。

对于默认配置,其意义就很明了了:当AOF文件的大小增长量达到原始大小的100%,并且AOF文件的大小大于64MB时,就会触发AOF重写。比如:

  • 原始AOF文件大小31MB,增长量达到100%时,其大小为:31 x (1 + 100%) = 62MB,仍然小于64MB,不触发AOF重写;
  • 原始AOF文件大小33MB,增长量达到100%时,其大小为:33 x (1 + 100%) = 66MB,已经大于64MB,会触发AOF重写。

这里,不必纠结于临界值的处理,因为Redis并不可能实时地去监控AOF文件的大小。

需要注意的是,当我们将auto-aof-rewrite-percentage配置项的值置为 0 时,我们就关闭了自动AOF重写这个功能。

日志重写的好处是,压缩了AOF文件,减少了磁盘占用量,因为将AOF文件中存在的命令压缩为了最小命令集,所以也提高了数据恢复的速度。

AOF文件修复

在运行Redis的系统崩溃时,可能出现AOF文件损坏的情况。此时,在Redis启动过程中,当AOF数据被加载回内存时,可能会发现AOF文件在末尾被截断。注意,这里是运行Redis的系统崩溃。当Redis本身崩溃或中止,但操作系统仍然正常工作时,这种情况不会发生。

当出现这种情况,Redis本身提供两种机制:

  • 当检测到AOF文件错误时,Redis无法成功启动,将会直接返回一个错误。
  • Redis成功启动,并且从损坏的AOF文件中加载尽可能多的数据。这是默认的处理机制。

这两种机制,由下面这个配置项进行控制:

aof-load-truncated yes

默认情况下,该配置项的值被置为 yes。在这种情况下,如果检测到AOF文件损坏,Redis会尝试尽可能多地加载AOF文件中的信息,并且输出相关的日志,用来告知客户端该事件;如果该配置的值被修改为 no,那么当AOF文件损坏时,服务将拒绝启动,并出现错误。此时,用户必须在重启Redis服务之前使用“redis-check-aof”程序修复AOF文件。在安装完Redis后,redis-check-aof可执行文件 和 redis-server 在同一个目录下 redis-5.0.5/bin 目录下。

redis-check-aof -fix appendonly.aof

这里,需要补充说明的是:如果发现AOF文件在中间损坏,服务器仍然会退出,并出现错误。只有当Redis试图从AOF文件中读取更多数据,但找不到足够的字节时,aof-load-truncated 选项才适用。

RDB前导

重写AOF文件时,Redis能够在AOF文件中使用RDB作为前导,以便更快地重写和恢复。打开此选项时,重写的AOF文件由两个不同的小节组成:[RDB文件][AOF尾部]。这个功能,默认是打开的:

aof-use-rdb-preamble yes

在加载AOF文件的时候,如果Redis识别到AOF文件是以“REDIS”字符串开头的(读取AOF文件的前5个字符),那么说明这是一个混合持久化的AOF文件。正确的RDB文件一定是以“REDIS”字符串开头,而纯AOF文件是以“*”开头的。在此时,就会去读取RDB文件,直到遇到“RDB_OPCODE_EOF”结束标记,此时再以AOF格式解析剩下的文件内容,直到整个加载过程完成。

RDB与AOF对比

默认情况下,Redis 5.0.5版本中,只开启了RDB快照持久化,而没有开启AOF。当然,我们通过配置,将两种持久化方式都打开。在这种情况下,当Redis重启恢复数据时,会优先恢复AOF文件中的数据,因为默认情况下,AOF的数据总是更加完整。

RDB的优点

  • RDB文件非常的紧凑,它保存了某个时间点的数据集。通过配置文件,我们可以很方便地定制持久化策略。
  • 因为RDB文件很紧凑,所以其很容易传送到远端数据中心,非常适用于灾难恢复。
  • RDB在保存RDB文件时,父进程唯一要做的就是 fork()出一个子进程,接下来的工作全部都由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化Redis性能。
  • 与AOF相比,在恢复一些大数据集的时候,RDB方式更快一些

RDB的缺点

  • RDB文件可能会造成一段时间的数据丢失。Redis保存一次完整的数据集是一个繁重的工作。Redis官方文档中建议,如果有必要,我们应该每隔5分钟或者更久 完成一次完整数据集持久化。在这种情况下,我们可能会丢失几分钟的数据。
  • SAVE命令会阻塞客户端的请求,直到同步完成才能再次接收客户端的请求。
  • RDB在fork()子进程来完成数据集持久化工作时,如果数据集过大,那么这个fork()操作也会非常地耗时。

AOF的优点

  • 使用AOF,我们的数据将会更加完整。我们可以通过配置,使用默认的fsync策略。即使使用默认的每秒fsync策略,Redis的性能依旧很好。fsync是由后台线程进行处理的,主线程会尽力处理客户端请求。一旦出现故障,我们最多损失1秒的数据。
  • AOF是一个只进行追加的日志文件。所以,如果在AOF文件写入过程中服务器断电,AOF文件不会有seeks(寻址)、损坏问题。即使出现问题,redis-check-aof工具也能够轻松修复它。

官方文档原文:

The AOF log is an append only log, so there are no seeks, nor corruption problems if there is a power outage. Even if the log ends with an half-written command for some reason (disk full or other reasons) the redis-check-aof tool is able to fix it easily.

  • AOF重写可以保证AOF文件也不至于过大,并且AOF文件重写的过程绝对安全。
  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行解析也很轻松。 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

AOF的缺点

  • 对于相同的数据集,AOF文件的体积通常要大于RDB文件的体积。
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入、载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

RDB与AOF的使用场景

  • 如果想要安全性第一,那我们应该同时使用RDB和AOF。
  • 如果我们能接受一段时间的数据丢失,那我们应该使用RDB。
  • Redis作者不推荐使用AOF。因为RDB更快,并且AOF还存在BUG。

问题解决:

1、Redis实例的数据集的大小为什么对BGSAVE进行fork()子进程操作的耗时有影响?

fork()操作,会创建一个子进程,而这个子进程是与父进程完全相同的。这就意味着,父进程的所有状态都会被复制,包括打开的文件、寄存器的状态和所有的内存分配,其中也包括程序代码。所以,当数据集太大的时候,这部分数据也会被复制到子进程,这时候,fork()操作就会变慢。

总结

至此,Redis持久化相关的知识就总结完毕了。如果有什么地方我没有描述清楚的,欢迎大家留言一起探讨。

参考文章

1、https://redis.io/topics/persistence

2、http://www.redis.cn/topics/persistence.html

3、redis.conf

4、http://oldblog.antirez.com/post/redis-persistence-demystified.html

5、https://www.toutiao.com/a6720120047622160910/

6、https://stackoverflow.com/questions/2731531/faster-forking-of-large-processes-on-linux

7、https://blog.csdn.net/jingkyks/article/details/46956905

8、https://yq.aliyun.com/articles/193034

9、https://www.bottomupcs.com/fork_and_exec.xhtml

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值