3.29--Redis总结之持久化篇---(温故而知新篇)--加油呀

Redis 是内存数据库,但是它为数据的持久化提供了两个技术:

AOF 日志和 RDB 快照

这两种技术都会用各用一个日志文件来记录信息,但是记录的内容是不同的。

AOF 文件的内容是操作命令
RDB 文件的内容是二进制数据

RDB 快照就是记录某一个瞬间的内存数据,记录的是实际数据
AOF 文件记录的是命令写操作的日志,而不是实际的数据

1.关于RDB快照

Redis 的快照是全量快照,每次执行快照,都是把内存中的「所有数据」都记录到磁盘中。

RDB 恢复数据的效率比 AOF 高,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据

1.Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave,区别就在于是否在「主线程」里执行:

  1. 执行了 save 命令,就会在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程;
    2.执行了 bgsave 命令,会创建一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞

执行快照时,数据能被修改吗?

执行 bgsave 过程中,Redis 依然可以继续处理操作命令

那具体如何做到到呢?关键的技术就在于写时复制技术

执行 bgsave 命令的时候,会通过 fork() 创建子进程
此时子进程和父进程是共享同一片内存数据的,因为创建子进程的时候,会复制父进程的页表,但是页表指向的物理内存还是一个

只有在发生修改内存数据的情况时,物理内存才会被复制一份:这样的目的是为了减少创建子进程时的性能损耗,从而加快创建子进程的速度,因为创建子进程的过程中,是会阻塞主线程的

创建 bgsave 子进程后,由于共享父进程的所有内存数据,于是就可以直接读取主线程(父进程)里的内存数据,并将数据写入到 RDB 文件

如果主线程(父进程)要修改共享数据里的某一块数据(比如键值对 A)时,就会发生写时复制,于是这块数据的物理内存就会被复制一份(键值对 A’),然后主线程在这个数据副本(键值对 A’)进行修改操作。与此同时,bgsave 子进程可以继续把原来的数据(键值对 A)写入到 RDB 文件。

极端情况下,如果所有的共享内存都被修改,则此时的内存占用是原先的 2 倍。(需要注意一下)

RDB 和 AOF 合体

混合持久化工作在 AOF 日志重写过程。

当开启了混合持久化时,在 AOF 重写日志时,fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据

这样的好处在于,重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样加载的时候速度会很快。
加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得数据更少的丢失。

关于AOF日志:

Redis 是先执行写操作命令后,才将该命令记录到 AOF 日志里

1.第一个好处,避免额外的检查开销。
2.第二个好处,不会阻塞当前写操作命令的执行,因为当写操作命令执行成功后,才会将命令记录到 AOF 日志。

1.第一个风险,执行写操作命令和记录日志是两个过程,当 Redis 在还没来得及将命令写入到硬盘时,服务器发生宕机了,这个数据就会有丢失的风险

2.第二个风险,由于写操作命令执行成功后才记录到 AOF 日志,所以不会阻塞当前写操作命令的执行,但是可能会给「下一个」命令带来阻塞风险

三种写回策略:
在这里插入图片描述

在 redis.conf 配置文件中的 appendfsync 配置项可以有以下 3 种参数可填:

1.Always,每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘;(可以最大程度保证数据不丢失,但是由于它每执行一条写操作命令就同步将 AOF 内容写回硬盘,所以是不可避免会影响主进程的性能)
2.Everysec,每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;
3.No,不由 Redis 控制写回硬盘的时机,转交给操作系统控制写回的时机,也就是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。

这三种策略只是在控制 fsync() 函数的调用时机

AOF 重写机制

AOF 重写机制是在重写时,读取当前数据库中的所有键值对,然后将每一个键值对用一条命令记录到「新的 AOF 文件」,等到全部记录完后,就将新的 AOF 文件替换掉现有的 AOF 文件

在使用重写机制后,就会读取最新的 (键值对) ,然后用一条命令记录到新的 AOF 文件

为什么重写 AOF 的时候,不直接复用现有的 AOF 文件,而是先写到新的 AOF 文件再覆盖过去?

如果 AOF 重写过程中失败了,现有的 AOF 文件就会造成污染,可能无法用于恢复使用

AOF 后台重写

写入 AOF 日志的操作是在主进程完成的,Redis 的重写 AOF 过程是由后台子进程 bgrewriteaof 来完成的

这么做可以达到两个好处:

  1. 子进程进行 AOF 重写期间,主进程可以继续处理命令请求,从而避免阻塞主进程;
    2.子进程带有主进程的数据副本,这里使用子进程而不是线程
    因为使用线程,多线程之间会共享内存,那么在修改共享内存数据的时候,需要通过加锁来保证数据的安全,而这样就会降低性能。
    使用子进程,创建子进程时,父子进程是共享内存数据的,不过这个共享的内存只能以只读的方式,而当父子进程任意一方修改了该共享内存,就会发生「写时复制」,于是父子进程就有了独立的数据副本,就不用加锁来保证数据安全。

写时复制:在发生写操作的时候,操作系统才会去复制物理内存
这样是为了防止 fork 创建子进程时,由于物理内存数据的复制时间过长而导致父进程长时间阻塞的问题

有两个阶段会导致阻塞父进程:

1.创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;
2.创建完子进程后,如果子进程或者父进程修改了共享数据,就会发生写时复制,这期间会拷贝物理内存,如果内存越大,自然阻塞的时间也越长;

重写 AOF 日志过程中,如果主进程修改了已经存在 key-value,此时这个 key-value 数据在子进程的内存数据就跟主进程的内存数据不一致了,这时要怎么办呢?

Redis 设置了一个 AOF 重写缓冲区,这个缓冲区在创建 bgrewriteaof 子进程之后开始使用。

在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会同时将这个写命令写入到 「AOF 缓冲区」和 「AOF 重写缓冲区」

在 bgrewriteaof 子进程执行 AOF 重写期间,主进程需要执行以下三个工作:

1.执行客户端发来的命令;
2.将执行后的写命令追加到 「AOF 缓冲区」;
3.将执行后的写命令追加到 「AOF 重写缓冲区」

当子进程完成 AOF 重写工作(扫描数据库中所有数据,逐一把内存数据的键值对转换成一条命令,再将命令记录到重写日志)后,会向主进程发送一条信号(信号是进程间通讯的一种方式,且是异步的)

主进程收到该信号后,会调用一个信号处理函数,该函数主要做以下工作:

1.将 AOF 重写缓冲区中的所有内容追加到新的 AOF 的文件中,使得新旧两个 AOF 文件所保存的数据库状态一致;
2.新的 AOF 的文件进行改名,覆盖现有的 AOF 文件。

信号函数执行完后,主进程就可以继续像往常一样处理命令了。

在整个 AOF 后台重写过程中,除了发生写时复制会对主进程造成阻塞,还有信号处理函数执行时也会对主进程造成阻塞,在其他时候,AOF 后台重写都不会阻塞主进程。

在这里插入图片描述

Redis 大 Key 对持久化有什么影响?

当 AOF 写回策略配置了 Always 策略,如果写入是一个大 Key,主线程在执行 fsync() 函数的时候,阻塞的时间会比较久,因为当写入的数据量很大的时候,数据同步到硬盘这个过程是很耗时的。

AOF 重写机制和 RDB 快照(bgsave 命令)的过程,都会分别通过 fork() 函数创建一个子进程来处理任务。会有两个阶段会导致阻塞父进程(主线程):

1.创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;
2.创建完子进程后,如果父进程修改了共享数据中的大 Key,就会发生写时复制,这期间会拷贝物理内存,由于大 Key 占用的物理内存会很大,那么在复制物理内存这一过程,就会比较耗时,所以有可能会阻塞父进程。

大 key 除了会影响持久化之外,还会有以下的影响:

1.客户端超时阻塞。由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。

2.引发网络阻塞。每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。

3.阻塞工作线程。如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。

4.内存分布不均。集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS(每秒查询率)也会比较大。

如何避免大 Key 呢?

1.在设计阶段,就把大 key 拆分成一个一个小 key。
2.定时检查 Redis 是否存在大 key ,如果该大 key 是可以删除的,不要使用 DEL 命令删除,因为该命令删除过程会阻塞主线程,而是用 unlink 命令(Redis 4.0+)删除大
key,因为该命令的删除过程是异步的,不会阻塞主线程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值