redis-持久化

news/2024/10/5 23:29:58

redis 数据持久化

作者:w08e

数据持久化三连问

redis 宕机数据会丢失吗

回答话术

先说结论,如果我们没开启任何持久化机制,那么会丢失全部数据,否则只会丢失部分数据,丢失数据的多少取决于持久化配置。Redis 提供了两套持久化机制,RDB 快照和 AOF 日志文件追加。

RDB 它会根据情况定期的 Fork 出一个子进程,生成当前数据库的全量快照。对于 RDB 快照,假如我们在 RDB 快照生成后宕机,那么会丢失快照生成期间全部增量数据,如果在连快照都没成功生成,那么就会丢掉全部数据

image.png

另一个是 AOF,它通过向 AOF 日志文件追加每一条执行过的指令实现。而当我们仅开启了 AOF 时,丢失数据的多少取决于我们设置的刷盘策略:当设置为每条指令执行后都刷盘 Always,我们最多丢失一条指令;当设置为每秒刷一次盘的 Eversec 时,最多丢失一秒内的数据;当设置为非主动刷盘的 No 时,则可能丢失上次刷盘后到现在的全部数据。

image.png

考虑到两种模式各有优缺点,没有一个适中的解决方案,为此,Redis 在 4.0 以后允许通过 aof‐use‐rdb‐preamble 配置开启混合持久化。

在开启混合持久化模式时,AOF 重写过程中,Redis 会将持久化数据以 RDB 格式写入 AOF 文件的开头,随后将后续的数据以 AOF 格式追加到文件末尾。恢复数据时,Redis 首先加载 RDB 数据,然后加载 AOF 增量数据。

image.png

问题详解

1. RDB

RDB 是 Redis 提供的持久化机制之一,它通过将内存中的数据保存到磁盘中的二进制文件来实现。

我们可以通过 SAVEBGSAVE 指令主动触发快照的生成,也可以通过配置文件中的 save 配置快照的自动生成条件。

在现实中出于性能考虑,我们不可能非常频繁的保存快照,因此要防止数据丢失,最终还是主要依靠 AOF 实现。

2. AOF

AOF 是 Redis 提供的另一套持久化机制。当每个写命令被执行完毕后,它们会被追加写入 AOF 日志文件的末尾。当 Redis 宕机以后,就可以通过 AOF 日志重放这些命令来恢复数据。

不过,每次命令执行后,数据会先写入 AOF 缓存,然后再写入操作系统缓存,最后才会根据刷盘策略真正的写入磁盘。因此刷盘策略真正决定了 Redis 宕机时会丢失多少数据:

  • Always:每执行一条指令就刷一次盘,宕机时最多丢失一条指令。
  • Eversec:每秒刷一次盘,宕机时最多丢失一秒内的数据。
  • No:不主动刷盘,由操作系统自己完成,宕机时最多丢失从上一次刷盘到宕机时的全部数据。

3. 混合持久化

混合持久化是在 AOF 基础上的优化措施,严格来说还是 AOF,Redis 官方文档里面持久化方式其实还是只有 AOF 和 RDB 两种。

由于 RDB 快照模式会丢失增量数据,AOF 文件较大会影响 Redis 数据恢复的时间,因此 Redis 在 4.0 以后允许通过 aof‐use‐rdb‐preamble 配置开启混合持久化。

当 AOF 重写时,它将会先生成当前时间的 RDB 快照,然后将其写入新的 AOF 文件头部位置,接着再把增量数据追加到这个新 AOF 文件中。如此一来,当 Redis 通过 AOF 文件恢复数据时,将会先加载 RDB,然后再重放后半部分的增量数据。这样就可以大幅度提高数据恢复的速度。

3.1. 查看混合持久化是否打开

根据 Redis 版本的不同,有些默认是开启的,有些默认是关闭状态,我们可以通过两种方式开启混合持久化配置。

127.0.0.1:6379> config get aof-use-rdb-preamble
1) "aof-use-rdb-preamble"
2) "no"
3.2. 通过命令行开启

可以通过 config set aof-use-rdb-preamble yes 命令行的方式开启混合持久化配置,注意开启后再查一次配置。

127.0.0.1:6379> config get aof-use-rdb-preamble
1) "aof-use-rdb-preamble"
2) "no"
127.0.0.1:6379> config set aof-use-rdb-preamble yes
OK
127.0.0.1:6379> config get aof-use-rdb-preamble
1) "aof-use-rdb-preamble"
2) "yes"
3.3. 文件配置开启

我们可以在 Redis 的配置文件 redis.conf 中开启,开启后确保万无一失需要再通过 config get aof-use-rdb-preamble 查询下。

# Redis can create append-only base files in either RDB or AOF formats. Using
# the RDB format is always faster and more efficient, and disabling it is only
# supported for backward compatibility purposes.
aof-use-rdb-preamble yes

Redis的RDB是怎么实现的?

img

回答话术

当通过 BGSAVE 指令生成 RDB 的时候,Redis 会 fork 出一个子进程,它会基于写时复制机制,在不阻塞主线程的情况下,将此刻的数据库全量数据保存为二进制快照。

具体的来说,在最开始的时候 fork 子进程的时候,操作系统会为其拷贝父进程的内存页,但是此时两者都指向同一块物理内存。当主进程发生写操作时,会真正的将父进程的数据拷贝到独立的物理内存中。此时父子进程的物理内存彼此独立,互不干涉,父进程继续处理增量数据,而子进程则根据拷贝出来的旧数据生成 RDB 快照。

image.png

相对 AOF,RDB 生成的二进制文件更小,数据恢复起来更快,并且整个流程中完全不会阻塞主进程,但是对应的,由于写时复制,在最坏的情况下可能会占用双倍内存,并且无法保存增量数据。

问题详解

1. 如何生成 RDB 文件

我们可以通过 SAVE 命令在主进程中阻塞的生成 RDB 文件,或者通过 BGSAVE 命令指定在子进程中生成 RDB 文件。

此外,也可以在数据库中通过 save 选项,指定当在一定的时间范围内执行了多少次修改时生成 RDB 文件。比如 save 120 10000 表示当在 120 秒内发生了 10000 次修改时,就通过 BGSAVE 在后台生成一分 RDB 文件。

2. 实现原理

当 Redis 在 fork 出一个子进程去生成 RDB 文件时,主进程依然还在不同的接受指令并操作数据。与 AOF 不同,由于 RDB 是快照,因此实际上它不会同步增量数据,不过也不会影响父进程的操作。Redis 通过写时复制机制实现这样的效果。

简单的来说,当 fork 子进程后,虽然子进程会从父进程中拷贝内存页,但是实际上的内存页依然还是指向了原本的物理内存。而当父进程修改了内存后,才会真正在物理内存中复制一遍父进程的数据,并让子进程的内存页指向这块物理内存,这就是写时复制。

根据这个原理,当子进程在生成 RDB 文件时,若父进程发生写操作,由于写时复制,子进程会得到一份父进程内存页的拷贝。此时子进程读取的是此时父进程内存数据的拷贝,而父进程继续操作他原本的那份内存,两者互不干涉。

我们都知道,在 Linux 系统中内存页一页是 4KB,由于是以内存页为单位的复制,所以在这个过程中,只要不是所有内存页上的数据都发生了修改,那么生成 RDB 文件过程中占用的额外内存是比较低的。

3. 与 AOF 的区别

RDB 即 Redis Database,它本质上就是某个时刻的全量数据快照。它以二进制的方式保存了某个时刻 Redis 数据库中的全部数据,Redis 启动后只需要将其加载进内存即可恢复数据。

相比起 AOF,它的优点是:

  • 文件更小:由于 RDB 里面存放的是非常紧凑的二进制数据,因此相比起 AOF 文件,它占用的空间更小,因此也更适合用来频繁的进行全量备份。
  • 加载更快:Redis 只需要将 RDB 加载进内存即可恢复数据,相比起还需要重放指令的 AOF,它要快得多。
  • 不阻塞主进程:生成 RDB 的过程可以完全在子进程中完成,因而不需要在主线程进行任何 IO 操作。

对应的,它的缺点是:

  • RDB 需要 fork 出子进程去完成这个任务,当比较频繁的生成 RDB 文件时,会对 CPU 带来比较大的压力;
  • 无法保存增量数据。

Redis的AOF是怎么实现的?

回答话术

AOF 即 Append Only File,它是 Redis 提供的一种持久化机制。

其原理是每当服务器执行写指令时,将命令追加到 AOF 日志文件。当 Redis 重新启动时,他会在本地启动一个伪客户端,并按顺序重新发送日志中的命令以恢复数据。

Redis 的 AOF 日志和 MySQL 的 binlog 有点像,当执行一个命令后,数据会先写入 AOF 缓冲区,再写入操作系统缓冲区,最后根据刷盘策略调用 fsync 函数将数据刷入磁盘。Redis 默认提供三种刷盘策略:Always(每个命令后都刷盘)、Everysec(每秒刷一次盘)、No(等到操作系统缓冲区满或定期刷盘)。

image.png

当 AOF 日志越来越大的时候,会触发 AOF 重写。举个例子,假如在 Redis 中对 1 递增了 99 次,那么 AOF 文件会记录一百条命令,但是实际上我们恢复数据的时候只需要一个最终值 100,中间的步骤都是不需要的。基于这个原理,在 Redis 重写的过程中,它会开启一个子进程扫描数据库,并生成一个新的 AOF 文件去替换旧的文件。这个文件将会比原本的文件精简,并且哪怕这个过程中 Redis 挂了,也不会影响已有的 AOF 文件。

不过,在子进程进行 AOF 重写的过程中,由于主进程还在不停的接受新的指令,因此它除了需要写自己的 AOF 缓冲区外,还需要将其写到 AOF 重写缓冲区中,以此实现重写过程中的增量数据同步。

问题详解

1. 保存哪些命令

在 AOF 文件中,只会保存写指令,或者更准确点说,只会保存修改数据的指令。

比如,我们依次执行了下述指令:

  1. RPUSH list 1 2 3 4
  2. LRANGE list 0 -1
  3. KEYS *
  4. LPUSH list 1

那么,最终只会保存两条:

  1. RPUSH list 1 2 3 4
  2. LPUSH list 1

2. 数据的保存格式

值得注意的是,在 Redis 中执行的命令并不会原模原样的保存到 AOF ,而是以一种比较特殊 $[长度] + [指令] 的格式保存。

比如,我们执行一个简单的命令 SET name open8gu,那么 AOF 中对应的内容如下:

*3
$3
SET
$4
name
$7
open8gu

上述命令的含义如下:

  1. *3 表示接下来有 3 个参数;
  2. $3 表示接下来的参数的长度为 3,SET 是命令名称;
  3. $4 表示下一个参数的长度为 4,name 是 Key 的名称;
  4. $7 表示下一个参数的长度为 7,open8gu 是要设置的值。

image.png

3. 写日志的时机

当 Redis 接收到一条指令的时候,它会先执行指令,然后再写 AOF 日志。

这个逻辑与我们熟悉的 MySQL 中的 binlog 不同,后者是写前日志(Write Ahead Log, WAL),即先写日志再保存数据,而 AOF 日志则是写后日志,即先保存数据再写日志。

这种处理方式的优点是:

  • 可以确保写入 AOF 日志指令都是没有错误的可执行的指令,避免写日志时还需要进行额外的语法/类型检查,或者等出错后回滚日志。
  • 不因为写日志而阻塞当前指令的执行。

不过对应的缺点也很明显:

  • 如果执行完指令实例突然挂了,那 AOF 日志中就不会记录这条指令。
  • 由于 Redis 的大多数命令都由单个线程执行,因此可能因为写日志而阻塞后一条指令的执行。

4. 日志的刷盘策略

实际上,将指令数据写入磁盘的时候,并不是一步完成的:

  1. 当执行了写指令后,数据首先被写入 Redis 自己的 AOF 缓冲区;
  2. 随后,Redis 会调用操作系统的 write 函数,将数据从 AOF 缓冲区写入操作系统缓冲区;
  3. 最后,再由 Redis 调用 fsync 函数或操作系统自己刷盘,让内核缓冲区中的数据真正写入磁盘。

第三步即我们通常说的“日志刷盘”。在日志真正的刷到磁盘之前,数据仍然仅保存在内存里,此时一旦服务器宕机,数据将会永久性的丢失。因此,何时刷盘是整个持久化流程的关键点。

在 MySQL 中,我们可以通过 binlog_sync 来指定 binlog 的刷盘策略,而在 Redis 中,我们可以通过 appendfsync 配置项指定 AOF 日志的的刷盘策略:

  • AOF_FSYNC_NO :AOF 缓冲区有数据时(即执行一个命令后),调用 write函数写入操作系统缓冲区,然后操作系统定期(在 Linux 中通常是 30 秒)或缓冲区满后再自动写入磁盘。
  • AOF_FSYNC_EVERYSEC :AOF 缓冲区有数据时(即执行一个命令后),调用 write函数写入操作系统缓冲区,然后每一秒钟调用一次 fsync 将数据写入磁盘。
  • AOF_FSYNC_ALWAYS :AOF 缓冲区有数据时(即执行一个命令后),立刻调用 fsync 将数据写入磁盘。

image.png

这三种配置方式各有优劣,它们会很大程度上的影响 Redis 的性能:

指令 时机 性能 宕机时丢失的数据
AOF_FSYNC_NO 不主动刷盘,由操作系统自己决定刷盘时机 所有未写入磁盘的数据
AOF_FSYNC_EVERYSEC 每秒保存一次 一秒内的数据
AOF_FSYNC_ALWAYS 每个命令执行后保存一次 一条指令的数据

总的来说,核心问题在于如何取舍可靠性与性能

  • 如果你的系统对数据可靠性要求极高,不允许数据丢失,那么你应该选择 ALWAYS
  • 如果你的系统更在乎性能,而不在意丢失一些数据,那么你可以选择 NO
  • 如果你想要在两者间取得平衡,那么你可以选择 EVERSEC

5. AOF 重写

5.1. 实现原理

随着写入操作的进行,AOF 文件会变得越来越大,而这其中的大多数数据是没必要保存的。

比如,你把 1 递增到 100,那么最终 AOF 会记录这一过程中的全部 100 条指令。然而实际上我们只需要最终的值 100 即可。

因此,AOF 提供了一种重写机制,即当 AOF 文件膨胀到一定程度时,Redis 将直接重新扫描当前数据库中的数据,然后把它们重写到一个新 AOF 文件中,并替换旧的 AOF 文件,这个新的 AOF 文件会比原本的文件更小。

在这个过程中,Redis 实际上完全不会重新读取原有的 AOF 文件。

5.2. 触发条件

在 2.4 版本以后,当你开启 AOF 功能,Redis 会在满足下述三个条件的时候自动触发 AOF 重写:

  • 当前没有正在执行的 AOF 重写或 RDB 生成操作。
  • 当前的 AOF 文件大于 server.aof_rewrite_min_size 配置。
  • 当前 AOF 的文件大小增幅达到设置的比例(比如比上一次重写后的文件大了 50%)。

除此之外,你也可以可以通过 BGREWRITEAOF 命令手动触发 AOF 重写。

5.3. 后台重写

作为一个非常重的 IO 操作,AOF 重写会长时间的阻塞线程,因此 Reids 会通过操作系统的 fork 函数分离出一个子进程 bgrewriteaof 来完成。

使用子进程的好处在于:

  • 子进程进行 AOF 重写时,主进程可以正常执行,避免阻塞。
  • 由于子进程带有主进程的数据副本,因此不需要像线程通过加锁控制对数据的访问。
5.4. 增量数据的同步

由于 AOF 重写基于父子进程,因此也带来一个问题:当子进程进行 AOF 重写时,主进程仍然还在接受指令修改数据,因此重写的 AOF 文件数据与实时数据就可能不一致。

对此,以 7.0 版本为分界线,Redis 采用了不同的处理方式:

5.4.1. 7.0 版本之前

在 7.0 之前,Redis 采用让主进程同时写两份 AOF 文件的方式来处理这个问题。

简单的来说,当子进程在进行 AOF 重写时,如果主进程接受了一个写指令,那么它在执行后,既要将这个指令追加到 AOF 缓冲区中,也需要将其加入 AOF 重写缓存中,相当于同时写两份文件。

当子进程完成 AOF 重写后,它会向父进程发送完成信号,此时父进程将阻塞的将 AOF 重写缓存区中的数据全部写入新的 AOF 文件中,然后使用新的 AOF 文件覆盖旧的 AOF 文件。至此, AOF 重写就完成了。

5.4.2. 7.0 版本之后

在 7.0 之后,当开始 AOF 重写的时候,主进程直接将增量数据写到一个全新的增量 AOF 文件中,等到子进程重写完 AOF,主进程再将增量 AOF 文件与重写后的 AOF 文件合并,并替换旧的 AOF 文件。

相比起 7.0 之前,对于同一个增量命令,主进程主需要往增量 AOF 文件里面写一次即可,不必再向子进程正在重写的 AOF 文件里面另外再写一条数据了。

需要注意的是,虽然我们称新的 AOF 文件为“增量 AOF”文件,不过对于主进程来说两个 AOF 文件没啥区别,只是从某一条命令开始换了一个文件写罢了,这条命令之前的数据全部在旧 AOF 文件,而这条命令及以后的数据都在新 AOF 文件里,这个过程依然受刷盘策略控制。

6. 数据的恢复

当 Redis 重新启动时,它会创建一个本地的伪客户端,这个客户端将会读取 AOF 日志,并且在还原出命令后发送给 Redis 服务端,直到全部的命令都执行完毕为止。

另外,根据官网文档,如果 Redis 在写 AOF 日志的过程中宕机,或者由于磁盘已满等不可抗力最终导致 AOF 日志出错,那么当重启时,Redis 会丢弃最后一个写入失败的指定,或者如果情况更糟糕,则可以通过 redis-check-aof工具尝试修复它。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/54721.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

Guava工具总结

Table双键Map public class OTest {public static void main(String[] args) {Map<String, Map<String, Integer>> map = new HashMap<>();//存放元素Map<String, Integer> workMap = new HashMap<>();workMap.put("Jan", 20);workMap…

B 端产品未来几年的发展趋势XG

在当今数字化高速发展的时代,B 端产品经理作为企业数字化转型的关键推动者,肩负着重大的责任。不仅要深入了解企业的业务需求,还要紧跟技术发展的步伐,为企业提供高效、创新的解决方案。那么,未来几年,B 端产品领域将会呈现出哪些发展趋势呢?一、人工智能与机器学习的深…

Clobotics 计算机视觉场景存储实践:多云架构、 POSIX 全兼容、低运维的统一存储HB

Clobotics 是一家将计算机视觉和机器学习技术应用于风电以及零售行业的企业。在风电行业,Clobotics 利用无人机对风力发电机叶片进行检查,显著降低了对人工作业的依赖。在零售领域,公司通过分析捕获的包装商品图像来提供基于实时数据的洞察,以增加销售额并减少运营成本。 存…

uni-app之camera组件-人脸拍摄i6

小程序录制视频;10-30秒;需要拍摄人脸,大声朗读数字(123456)这种。 1.camera组件 camera页面内嵌的区域相机组件。注意这不是点击后全屏打开的相机 camera只支持小程序使用;官网链接1.2 效果图1.3 页面布局 camera 设置宽100%,高度通过uni.getSystemInfo获取,全屏展示。…

怎样打开windows自动更新,打开电脑更新的步骤及方法

清理C盘是维护电脑性能的重要步骤,但需要注意方法以避免对电脑造成不良影响。以下是一份详细的C盘清理教程及方法,旨在帮助您安全有效地释放C盘空间: 一、备份重要数据 步骤:在开始清理前,请确保您已备份C盘上的重要个人文件和系统设置,以防误删或意外情况导致数据丢失。…

GPU虚拟化技术简介:实现高性能图形处理的灵活部署

GPU虚拟化技术是一项重要的创新,通过将物理GPU划分为多个虚拟GPU,实现多用户共享和独立运行图形处理任务的能力。本文介绍了GPU虚拟化技术的基本原理、应用场景和优势。该技术在云计算、虚拟桌面基础设施和科学研究等领域具有广泛应用。GPU虚拟化技术提高了资源利用率、性能和…

二. Spring Boot 中的 “依赖管理和自动配置” 详解透彻到底(附+详细代码流程)sh

二. Spring Boot 中的 “依赖管理和自动配置” 详解透彻到底(附+详细代码流程) @ 目录* 二. Spring Boot 中的 “依赖管理和自动配置” 详解透彻到底(附+详细代码流程)1. 如何理解 “ 约定优于配置 ” 2. Spring Boot 依赖管理 和 自动配置2.1 Spring Boot 的依赖管理2.1.1…

从代码到产品,我的IT职业成长之路yW

每个人的职业生涯都是一段充满转折和挑战的旅程,当然每一次职业转型都是一次重新定义自己的机会,从2015年开始,当时我刚踏入IT行业,成为一名Java开发者,后来随着时间的推移,我的职业方向逐渐转向了前端开发者,埋头于代码的世界。最终在2018年找到了属于自己的职业定位—…