为什么要做持久化存储?
我们都知道 Redis 是一个基于内存的 nosql 数据库,内存存储很容易造成数据的丢失,因为当服务器关机等一些异常情况都会导致存储在内存中的数据丢失。
-
持久化存储就是将 Redis 存储在内存中的数据存储在硬盘中,实现数据的永久保存。(主要原因)
-
数据的传输、拷贝。(作用)
-
开发、测试。(作用)
持久化存储分类
在 Redis 中,持久化存储分为两种。一种是 aof 日志追加的方式,另外一种是 rdb 数据快照的方式。
RDB 持久化存储
什么是RDB持久化存储?
RDB 持久化存储即是将 redis 存在内存中的数据以快照的形式保存在本地磁盘中。 RDB持久化存储分为自动备份和手动备份
1.手动备份
通过 save 命令和 bgsave 命令。save 是同步阻塞,而 bgsave 是非阻塞(阻塞实际发生在 fork 的子进程中)。在我们实际过程中大多是使用 bgsave 命令实现备份。
// 阻塞直到命令处理完毕响应OK
redis> save
OK
// fork一个独立线程实现持久化
redis> bgsave
Background saving started
2.自动备份
修改配置项 save m n 即表示在 m 秒内执行了 n 次命令则进行备份。
实则是通过bgsave处理
3.save与bgsave不同场景的使用
-
当 Redis 从服务器项主服务器发送复制请求时,主服务器则会使用 bgsave 命令生成 rbd 文件,然后传输给从服务器。如果是存在多个slave节点,只会执行一次bgsvae命令。
-
当执行 debug reload 命令时也会使用 save 命令生成 rdb 文件。
-
当使用 shutdown 命令关掉服务时,如果没有启用 aof 方式实现持久化则会采用 bgsave 的方式做持久化。同时 shutdown 后面可以加备份参数[nosave|save]。
bgsave持久化存储实现原理
-
执行 bgsave 命令,Redis 父进程判断当前是否存在正在执行的子进程,如果存在则直接返回。
-
父进程 fork 一个子进程(fork 的过程中会造成阻塞的情况),这个过程可以使用 info stats 命令查看 latest*fork_usec 选项,查看最近一次 fork 操作小号的时间,单位是微妙。
-
父进程 fork 完之后,则会返回 Background saving started 信息提示,此时 fork 阻塞解除。
-
fork 出的子进程开始根据父进程内存数据生成临时的快照文件,然后进行原子操作替换原文件。使用 lastsave 命令可以查看最后一次生成 rdb 的时间,对应 info 的 rdb_last_save_time 选项。
-
当备份完毕之后向父进程发送完成信息,此时父进程会更新最新的持久化统计信息。具体可以见 info Persistence 下的 rbd**选项。
RDB持久化的优势与劣势
优势
-
文件实现的数据快照,全量备份,便于数据的传输。比如我们需要把 A 服务器上的备份文件传输到 B 服务器上面,直接将 rdb 文件拷贝即可。
-
文件采用压缩的二进制文件,当重启服务时加载数据文件,比 aof 方式更快(aof是重新去执行一次命令)。
劣势:
-
rbd 采用加密的二进制格式存储文件,由于 Redis 各个版本之间的兼容性问题也导致 rdb 由版本兼容问题导致无法再其他的 Redis 版本中使用。
-
实时性差,并不是完全的实时同步,容易造成数据的不完整性。因为 rdb 并不是实时备份,当某个时间段 Redis 服务出现异常,内存数据丢失,这段时间的数据是无法恢复的,因此易导致数据的丢失。
-
可读性差,由于文件内容采用二进制加密处理,我们无法直接读取,不能修改文件内容。(这种情况因为极少极少,相比AOF算是一个劣势吧)。
RDB 文件常见的问题处理方式总结
- 当遇到磁盘写满情况,可以使用如下命令来切换存储磁盘
// dirName则是新的存储目录名(该方式同样适用于aof格式)
config set dir dirName
- 文件压缩处理,虽然对 CPU 具有消耗,但是减少体积的暂用,同时做文件传输(主从复制)也减少消耗.
// 修改压缩开启或关闭
config set rdbcompression yes|no
- rbd 备份文件损坏检测.可以使用 redis-check-rdb 工具检测 rdb 文件,该工具默认在/usr/local/bin/目录下面.
[root@syncd redis-data]# /usr/local/bin/redis-check-rdb ./6379-rdb.rdb
[offset 0] Checking RDB file ./6379-rdb.rdb
[offset 26] AUX FIELD redis-ver = '5.0.3'
[offset 40] AUX FIELD redis-bits = '64'
[offset 52] AUX FIELD ctime = '1552061947'
[offset 67] AUX FIELD used-mem = '852984'
[offset 83] AUX FIELD aof-preamble = '0'
[offset 85] Selecting DB ID 0
[offset 105] Checksum OK
[offset 105] \o/ RDB looks OK! \o/
[info] 1 keys read
[info] 0 expires
[info] 0 already expired
AOF 持久化存储
AOF持久化存储是什么
-
由于RDB持久化方式不能做到实时存储,而AOF可以更具不同的策略实现实时持久化。
-
AOF 持久化存储便是以日志的形式将 aof_buf 缓冲区中的命令写入到磁盘中。简而言之,就是记录 redis 的操作日志,将 redis 执行过的命令记录下来。
-
当我们需要数据恢复时或者重启Redis服务时,Redis再去重新执行一次日志文件中的命令,以达到数据重载的目的。
如何配置持久化存储
// 将no改为yes,控制aof开启与否
appendonly no
// 控制aof文件名称,存储的目录便是dir配置项
appendfilename "appendonly.aof"
// 三种备份策略(三者只需要开启以一个即可)
# appendfsync always // 命令写入立即写入磁盘
appendfsync everysec // 每秒实现文件的同步,写入磁盘
# appendfsync no // 随机进行文件的同步,同步操作则交给操作系统来负责,通常时间是最长30s
当命令写入到AOF缓冲区后,Redis会根据持久化存储策略,单独开一个AOF线程来实现写入到磁盘操作。
AOF持久化存储实现原理
aof 日志追加方式实现持久化存储,需要经历如下四个过程.命令写入->文件同步->文件重写->文件重载。
-
redis 命令写入,此时会将 redis 命令写入 aof_buf 缓冲区.。
-
缓冲区中数据根据备份策略实现写入日志文件。
-
当 aof 的文件越来越庞大,会根据我们的配置策略来实现 aof 的重写,实现文件的压缩,减少体积。
-
当 redis 重新启动时,在去重写加载 aof 文件,达到数据恢复的目的。
命令写入
命令写入主要是将文件执行过的命令写入到日志文件中。并且日志文件尊徐文本协议格式,下面示例代码便是 aof 日志文件中存储的内容格式.
*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n
AOF文件格式的分析
-
AOF持久化存储的文本格式。这样兼容性更好。前面我们提及到了 rdb 文件是进行二进制加密,可能不同版本之间会出现不兼容的情况,采用文本协议可以加避免该问题。同时文本协议也可以减少跨平台使用所带来的诸多问题。
-
可读性强。由于 aof 是将命令写入文件中,我们可以直接查看命令内容,同时也可以修改日志文件内容。
-
开启 aof 后,所有的文件文件都包含追加操作,直接采用文本协议,减少二次开销(这一点,个人不是很理解.因为我们的 aof 是保存的是命令,当我们再次去加载的时候,会去执行一次里面的命令,当文件大的时候应该是比较耗时的吧。如果没有做好文件重写策略,大量重复无效的命令执行,对于二进制加密的 rdb 格式,不需要再去转换,这一点确实可以减少二次开销)。
文件写入策略
文件写入是将 aof_buf 缓冲区的命令写入到文件中,然后再将缓冲区的内容写入到磁盘中。文件写入的策略有如下三种方式:
配置项 | 配置说明 |
---|---|
always | 命令写入到 aof_buf 缓冲区中之后立即调用系统的<font color='red'>fsync 操作</font>同步到 aof 文件中,fsync 完成后线程返回. |
everysec | 命令写入到 aof_buf 缓冲区后,调用系统的<font color='red'>write 操作</font>,write 完成后线程返回。最后每隔一秒做fsyc操作。 |
no | 命令写入 aof_bug 缓冲区后调用系统 write 操作,不对 aof 文件做 fsync 同步,同步硬盘操作由<font color='red'>系统操作</font>完成,时间一般最长为 30s. |
系统调用 write 和 fsync 说明
-
write 操作会触发延迟写( delayed write) 机制。 Linux 在内核提供页缓冲区用来提高硬盘 IO 性能。 write 操作在写入系统缓冲区后直接返回。 同步硬盘操作依赖于系统调度机制, 例如: 缓冲区页空间写满或达到特定时间周期。 同步文件之前, 如果此时系统故障宕机, 缓冲区内数据将丢失。
-
fsync 针对单个文件操作( 比如 AOF 文件),做强制硬盘同步, fsync 将阻塞直到写入硬盘完成后返回, 保证了数据持久化。
文件写入策略分析
-
配置为 always 时, 每次写入都要同步 AOF 文件, 在一般的 SATA 硬盘上, Redis 只能支持大约几百 TPS 写入, 显然跟 Redis 高性能特性背道而驰,不建议配置。
-
配置为 no。由于操作系统每次同步 AOF 文件的周期不可控, 而且会加大每次同步硬盘的数据量, 虽然提升了性能, 但数据安全性无法保证。
-
配置为 everysec。是建议的同步策略, 也是默认配置, 做到兼顾性能和数据安全性。 理论上只有在系统突然宕机的情况下丢失 1 秒的数据。
文件重写
为什么要文件做文件重写操作
由于 aof 采用的是日志追加,我们 redis 命令不断的写入,aof 文件的体积会不断的增加,为了减小aof文件的体积。因此 redis 引入了 aof 重写机制达到减小 aof 文件体积。
aof 文件重写是把 redis 进程内的数据转换为写命令同步到新的 aof 文件的过程。
文件重写有什么好处 文件重载主要优化的地方有如下三点。使用文件重载既可以减少文件的体积,同时去掉了一些无效的操作,可以加快文件重载效率.
-
将一些在进程内无效的数据不在写入新的文件。如过期的键。
-
去掉一些无效的命令。如 del key1。
-
简化操作。如 lpush list a,lpush list b,直接可以简化为 lpush list a b. 3.文件重载由那些方式。
文件重载触发机制
-
自动触发机制。 直接使用 bgrewriteaof 命令即可.该命令在 fork 子进程的时候会发生阻塞。
-
手动触发机制。
auto-aof-rewrite-min-size:aof 重写时文件最小的体积,默认的是 64M。 auto-aof-rewrite-percentage:代表当前 AOF 文件空间( aof_current_size) 和上一次重写后 AOF 文件空间( aof_base_size) 的比值。
自动触发时机=aof_current_size>auto-aof-rewrite-minsize&&
( aof_current_size-aof_base_size) /aof_base_size>=auto-aof-rewritepercentage
其中 aof_current_size 和 aof_base_size 可以在 info Persistence 统计信息中查看。
文件重写实现原理
流程分析:
- 执行重写命令,判断是否存在子进程。 如果已经有子进程在进行 aof 重写,则会提示如下信息。
ERR Background append only file rewriting already in progress
如果已经存在子进程在进行 bgsave 操作,重写命令会延迟到 bgsave 命令完成之后进行,会返回如下信息。
Background append only file rewriting scheduled
-
父进程会 fork 一个子进程,在 fork 子进程的过程中会造成阻塞。
-
fork 子进程结束阻塞解除,进行其他新的命令操作.新的命令依旧根据文件写入策略同步数据,保证 aof 机制正确进行(图中 3.1).。
-
子进程在进行写的过程中,由于** fork 操作运用的是写时复制技术,子进程只能共享 fork 操作时内存保留的数据**,新的数据是无法操作的。父进程在这过程中仍然在响应其他的命令,于是 Redis 会使用 aof 重写缓存区来保存这部分新的数据。
-
子进程进行根据重写规则将数据写入到新的 aof 文件中,并且每次写入有大小限制,通过 aof-rewrite-incremental-fsync 配置项来控制,默认是 32M,这样可以见减少单次刷盘(I/O 写)造成硬盘阻塞。
-
子进程在完成重写之后,会向父进程发送信息,父进程更新统计信息.可参看 info persistence 下的 aof_*相关统计。
-
父进程会把新写入存在 aof 重写缓冲区的数据写入到 aof 文件中。
-
父进程将新的 aof 文件替换掉旧的 aof 文件。
父进程负责将aof-rewrite-aof的数据写入到新AOF文件中,此时Redis是一个阻塞过程,写入命令就会阻塞等待直到AOF文件替换完毕,执行完毕之后在去处理阻塞的写入命令。这样保证了数据的一致性。
问题一
在第 3 和 4 中,其实不是特别理解.不理解的是为什么父进程在响应新的命令会写入旧的 aof 文件,还要 aof 重写缓存区?
AOF持久化使用了写时复制技术,子进程只能获取到fork时的内存数据。在重写过程中,存在有新的命令写入,当重写完成之后,直接把aof_rewrite_buff数据写入到新的备份文件,这样保证了数据的完整性。
问题二
在第7和8中为什么是父进程进行操作?
这时候使用父进程操作,新的写命令就会发生阻塞,知道父进程处理完毕。这样减少了数据的丢失,保持了数据的完整性。
文件重载
文件重载就是将文件重新加入到 redis 服务中.比如 redis 服务重启用于数据恢复.redis 的重载机制非常完善,具体流程如下。
AOF文件常见的问题处理
1.文件损坏 我们在加载损坏的文件是可能提示如下信息.
Bad file format reading the append only file:
make a backup of your AOF file,then use ./redis-check-aof --fix <filename>
此时我们可以使用 redis-check-aof --fix 命令进行修复(记得对文件做个备份).修复后使用 diff-u 进行数据对比,找出部分丢失的数据. 2.文件加载不完整 这可能是数据在备份的时候,redis 服务异常,导致备份不完整.可以使用 redis 的 aof-load-truncated 兼容该异常
AOF的优缺点
优点: 多种文件写入(fsync)策略. 数据实时保存,数据完整性强.即使丢失某些数据,制定好策略最多也是一秒内的数据丢失. 可读性强,由于使用的是文本协议格式来存储的数据,可有直接查看操作的命令,同时也可以手动改写命令. 缺点: 文件体积过大,加载速度比 rbd 慢.由于 aof 记录的是 redis 操作的日志,一些无效的,可简化的操作也会被记录下来,造成 aof 文件过大.但该方式可以通过文件重写策略进行优化.
RDB配置
- rdb持久化频率。
save 900 1
save 300 10
save 60 10000
- bgsave持久化出现问题,Redis是否停止写入命令。
stop-writes-on-bgsave-error yes
- 是否开启文件压缩。
rdbcompression yes
- 保存或者加载rdb文件时是否对进行校验。
rdbchecksum yes
- rdb文件名称。
dbfilename 6379.rdb
- rdb文件存储目录。
dir /data/6379
AOF文件
- 是否开启AOF持久化。
appendonly yes
- 持久化文件名。
appendfilename "appendonly.aof"
- 持久化同步策略。everysec:每秒,always:写入缓冲区立即写入磁盘,no:由系统自动处理到磁盘。
appendfsync everysec
- 当开启aof持久时,一方面存在子进程对aof文件做重写,另一方面新的写入到aof_buffer后,做磁盘写入。当两者发生冲突时,是否让磁盘写入可操作。如果是no则不会写入到磁盘,而是继续写入aof_buffer。这样可能会造成最多30秒的数据丢失。
no-appendfsync-on-rewrite no
- 当前 AOF 文件增长量超过上次 AOF 文件大小的 100% 时,就会触发 background rewrite。若配置为 0,则会禁用自动 rewrite。
auto-aof-rewrite-percentage 100
指定触发 rewrite 的AOF文件大小。若AOF文件小于该值,即使当前文件的增量比例达到auto-aof-rewrite-percentage的配置值,也不会触发自动 rewrite。即这两个配置项同时满足时,才会触发rewrite。
auto-aof-rewrite-min-size 64mb
- 文件重载时,是否对文件尾部不完整兼容。当yes时,只将错误信息发送到log,尽可能的加载更多的数据。如果是no则需要使用redis-check-aof对文件做修复才可重载。
aof-load-truncated yes
- 是否开启混合(AOF和RDB)持久化。
aof-use-rdb-preamble no
常见问题
1. 选择AOF还是RDB进行数据的持久化
-
针对不同的情况来选择,建议使用两种方式相结合。
-
针对数据安全性、完整性要求高的采用 aof 方式。
-
针对不太重要的数据可以使用 rdb 方式。
-
对于数据进行全量备份,便于数据备份的可以采用 rdb 方式。
2. 使用AOF方式做持久化时,从写操作时fork出来的子进程操作,此时父进程中的数据是如何做到合并的
-
fork出来的子进程在重写完成之后,会通知父进程。
-
父进程在接收到子进程通知时,不仅会更新重写的信息,并且会暂时停止新的命令写入。
-
在重写过程中,对rewrite-aof-buffer中的数据追加到重写好的文件中。
-
执行完之后,对原有的文件进行替换。接着处理新的命令。
重写操作时对fork时的父进程内数据进行重写,而不是对原有的aof进行重写。重写操作用到了写时复制技术,子进程只能获取到fork时父进程的数据,因此需要将新的命令写入到rewrite-aof-buffer,来保证数据的完整性。