Redis持久化详解

持久化

RDB

RDB全称Redis Database Back file,也被叫做redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当redis实例故障重启后,从磁盘读取快照文件,恢复数据。

快照文件称为RDB文件,save命令默认是保存在当前运行目录。 ##不推荐使用 

 

在停机之前默认会触发一次,但是碰到宕机就不会保存。

在redis.conf中配置

 

配置的命令虽然是save,但是会执行bgsave命令

bgsave

bgsave开始时会fork()【fork()由Redis主线程调用主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入rdb文件。

子进程->获取并复制父进程虚拟内存(页表)->读取物理内存

fork采用的是copy-on-write(写时复制)技术:

当主进程执行读操作时,访问共享内存;

当主进程执行写操作时,则会拷贝一份数据,执行写操作。

(kernel会把主进程中的所有内存页的权限都设为read-only,主进程和子进程访问数据的指针都指向同一内存地址。主进程发生写操作时,因为权限已经设置为read-only了,所以会触发页异常中断(page-fault)。在中断处理中,需要被写入的 内存页面会复制一份,旧数据交给子进程使用,然后主进程该干啥就干啥。)

 

在bgsave时,主进程写的时候会会复制物理内存的数据给主进程读写。

那么子进程读取的还是原来的物理内存,要是宕机了则新的数据就会丢失。

并且极端条件下,如果所有fork的内存都要进行写操作,那么都将在物理内存分配一个新的空间给主进程写,内存消耗变成两倍。

阻塞父进程的原因:

  • 创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;

  • 创建完子进程后,如果子进程或者父进程修改了共享数据,就会发生写时复制,这期间会拷贝物理内存,如果内存越大,自然阻塞的时间也越长;

  •  

 

AOF

AOF全称为append only file(追加文件,redis处理的每一个写命令都会记录在aof文件,可以看做是命令日志文件。

 

 

 

可以看出来是在命令成功之后才记录日志:

好处

1:避免额外的开销。 因为语法有问题就白记录了,在命令结束后记录也能减少检查开销

2:不会阻塞当前写操作的执行指令。

风险:

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

2:可能会给「下一个」命令带来阻塞风险

在AOF中,如果AOF缓冲区的文件同步策略为everysec,则在主线程中,命令写入aof_buf后调用操作系统write操作,write完成后主线程返回;fsysnc同步文件操作由专门的文件同步线程每秒调用一次。

这种做法的问题在于,如果硬盘负载过高,那么fsysnc操作可能会超过1s;如果Redis主线程持续高速向aof_buf写入命令,硬盘的负载可能会越来越大,IO资源消耗会更快。如果此时Redis异常退出,会导致数据丢失可能远超过1s。

为此,Redis的处理策略是这样的:主线程每次进行AOF会对比上次fsync成功的时间;如果距上次不到2s,主线程直接返回;如果超过2s,则主线程阻塞直到fsync同步完成。因此,如果系统硬盘负载过大导致fsync速度太慢,会导致Redis主线程的阻塞;此外,使用everysec配置,AOF最多可能丢失2s的数据,而不是1s。

 

**AOF流程!

 

 

三个写回策略

 

实际上这三个策略不同的就是fsync()的调用时机,

  • Always 策略就是每次写入 AOF 文件数据后,就执行 fsync() 函数;主进程执行

  • Everysec 策略就会创建一个异步任务来执行 fsync() 函数;异步执行

  • No 策略就是永不执行 fsync() 函数。

AOF重写机制

读取所有键值对 -> 生成一个新的aof文件 -> 写入 ->若重写缓存区有新的,写入 ->替换原来的aof文件

Redis 为了避免 AOF 文件越写越大,提供了 AOF 重写机制,当 AOF 文件的大小超过所设定的阈值后,Redis 就会启用 AOF 重写机制,来压缩 AOF 文件。

 

在使用重写机制后,就会读取 name 最新的 value(键值对)(直接读取Redis内存中的数据) ,然后用一条 「set name xiaolincoding」命令记录到新的 AOF 文件

因为是记录命令,AOF文件会比RDB文件大的多,而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令(后台子进程执行),可以让AOF文件执行重写功能,用最少的命令打到相同的效果。

bgrewriteaof好处:

  • 子进程进行 AOF 重写期间,主进程可以继续处理命令请求,从而避免阻塞主进程;

  • 子进程带有主进程的数据副本(写时复制,与bgsave一样),这里使用子进程而不是线程,因为如果是使用线程,多线程之间会共享内存,那么在修改共享内存数据的时候,需要通过加锁来保证数据的安全,而这样就会降低性能。而使用子进程,创建子进程时,父子进程是共享内存数据的,不过这个共享的内存只能以只读的方式,而当父子进程任意一方修改了该共享内存,就会发生「写时复制」,于是父子进程就有了独立的数据副本,就不用加锁来保证数据安全。

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

为了解决这种数据不一致问题,Redis 设置了一个 AOF 重写缓冲区,这个缓冲区在创建 bgrewriteaof 子进程之后开始使用。

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

 

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

  • 执行客户端发来的命令;

  • 将执行后的写命令追加到 「AOF 缓冲区」(正常状态下aof存储缓冲区,等空闲了写进aof文件中);

  • 将执行后的写命令追加到 「AOF 重写缓冲区」;

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

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

  • 将 AOF 重写缓冲区中的所有内容追加到新的 AOF 的文件中,使得新旧两个 AOF 文件所保存的数据库状态一致;

  • 新的 AOF 的文件进行改名,覆盖现有的 AOF 文件。

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

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

Redis也会在触发阈值时重写AOF文件。阈值也可以在redis.conf中配置:

#AOF文件比上次文件 增长超过多少百分比则触发重写

auto-aof-rewrite-percentage 100

#AOF文件体积最小多大以上才触发重写

auto-aof-rewrite-min-size 64mb

AOF与RDB对比

 

AOF与RDB合体

这个方法是在 Redis 4.0 提出的,该方法叫混合使用 AOF 日志和内存快照,也叫混合持久化。

如果想要开启混合持久化功能,可以在 Redis 配置文件将下面这个配置项设置成 yes:

 

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

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

也就是说,使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据

 

这样的好处在于,重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样加载的时候速度会很快(因为rdb是直接复制内存中现有的数据到磁盘里,而aof要筛选合并原来aof文件中的命令)。

加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得数据更少的丢失。(即aof重写缓存区中的内容)

面试题:Redis大key对持久化的影响

一、大key对AOF三个写回策略日志的影响

在使用 Always 策略的时候,主线程在执行完命令后,会把数据写入到 AOF 日志文件,然后会调用 fsync() 函数,将内核缓冲区的数据直接写入到硬盘,等到硬盘写操作完成后,该函数才会返回。

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

当使用 Everysec 策略的时候,由于是异步执行 fsync() 函数,所以大 Key 持久化的过程(数据同步磁盘)不会影响主线程。

当使用 No 策略的时候,由于永不执行 fsync() 函数,所以大 Key 持久化的过程不会影响主线程。

二、大 Key 对 AOF 重写和 RDB 的影响

1.

当 AOF 日志写入了很多的大 Key,AOF 日志文件的大小会很大,那么很快就会触发 AOF 重写机制

2.

在通过 fork() 函数创建子进程的时候,虽然不会复制父进程的物理内存,但是内核会把父进程的页表复制一份给子进程,如果页表很大,那么这个复制过程是会很耗时的,那么在执行 fork 函数的时候就会发生阻塞现象

而且,fork() 函数是由 Redis 主线程调用的,如果 fork ()函数发生阻塞,那么意味着就会阻塞 Redis 主线程。由于 Redis 执行命令是在主线程处理的,所以当 Redis 主线程发生阻塞,就无法处理后续客户端发来的命令。

fork()是主线程调用,子线程来完成复制操作

3.

写时复制时,如果创建完子进程后,父进程对共享内存中的大 Key 进行了修改,那么内核就会发生写时复制,会把物理内存复制一份,由于大 Key 占用的物理内存是比较大的,那么在复制物理内存这一过程中,也是比较耗时的,于是父进程(主线程)就会发生阻塞

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

  • 创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;

  • 创建完子进程后,如果子进程或者父进程修改了共享数据,就会发生写时复制,这期间会拷贝物理内存,如果内存越大,自然阻塞的时间也越长;

三、总结

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

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

  • 创建子进程的途中,由于要复制父进程的页表等数据结构,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;

  • 创建完子进程后,如果父进程修改了共享数据中的大 Key,就会发生写时复制,这期间会拷贝物理内存,由于大 Key 占用的物理内存会很大,那么在复制物理内存这一过程,就会比较耗时,所以有可能会阻塞父进程

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

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

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

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

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

如何避免大 Key 呢?

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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis持久化是为了避免进程退出导致数据的永久丢失而设计的。由于Redis是基于内存的数据库,数据存储在内存中,关闭服务或者断电导致数据丢失。为了解决这个问题,Redis提供了两种持久化方式:AOF(Append Only File)和RDB(Redis Database File)。 AOF持久化是通过将写操作追加到AOF文件中来实现的。AOF文件是一个只追加的日志文件,记录了写操作的命令。当Redis重启时,Redis根据AOF文件中的命令重新执行一遍,从而恢复数据。AOF文件的大小随着写操作的增加而增大,因此可能占用较大的磁盘空间。为了避免AOF文件过大,Redis提供了AOF重写机制,可以定期地将AOF文件重写为紧凑格式,只保留可以恢复数据库状态的最小命令集合。 RDB持久化是通过将当前数据库状态快照保存到一个二进制文件中来实现的。RDB文件是一个经过压缩的二进制文件,包含了数据库的数据和键值对的过期时间等信息。RDB持久化是通过fork子进程来实现的,它将当前数据库状态保存到一个临时文件中,然后替换原来的RDB文件。RDB持久化适用于数据备份和灾难恢复。 除了持久化之外,Redis还支持快照机制。快照是将当前数据库状态保存到一个RDB文件中,可以手动触发或者通过配置选项定期触发。快照只保存了数据库的最新状态,而不是增量的写操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值