Redis(三):持久化机制

目录

前言

AOF

RDB

混合持久化

番外:大key问题


前言

        假如我把 Redis 的进程关掉了,缓存的数据是不是都没了?!

        不慌不慌,Redis 这不有持久化机制的嘛

        Redis 持久化机制设计的初衷是为了故障恢复,假如出现宕机,可以加载AOF并执行命令,或者通过加载RDB快照的方式,来实现数据恢复,它并不是实现像关系型数据库那样的实时持久化。


AOF

AOF日志,是把每一次的写操作命令记录下来,重启 Redis 时读取并执行这个文件中的命令,实现数据恢复。

先执行命令,再把命令记录到AOF日志中

好处:

1.避免额外的检查开销:执行完再记录,可以确保记录到AOF文件中的命令是可以执行的、并且是执行成功的,就不需要再额外检查了

2.不会阻塞当前命令的执行

坏处:

1.【执行写操作】和【把命令记录到日志中】是两个步骤,不是原子性的,假如 Redis 在执行完写操作之后、记录命令之前,出现宕机或故障,数据就有可能丢失

2.这样确实不会阻塞当前命令的执行,但是可能会阻塞下一条命令呀...


重写机制

AOF日志是一个文件,记录的是每一条执行的写命令,随着执行的写操作越来越多,就会有越来越多的命令被装进去,文件的体积就会越来越大。当我们再读取并执行AOF中记录的命令时,加载速度和恢复效率就会特别低。。

通俗点,咱们加载一个轻量级的小文件和加载一个庞大笨重的大文件,结果能一样嘛...

所以,Redis 提供了重写机制:

尽管某个键值对被多条命令反复地修改,但最终只根据这个键值对的最新状态,只用1条写命令记录键值对。全部写完之后,把新的AOF文件替换掉原有的AOF文件。

比如,假设前后执行了「set name a」和「set name b」这两个命令:

  • 如果没有使用重写机制,就会将这两个命令全都记录到 AOF 文件;
  • 使用重写机制后,就会读取name的最新的值,只使用一条「set name b」命令记录到新的AOF文件

(尽管你千变万化,但我只记录你最终的1个最新状态,妙!)


[AOF重写] 和 [RDB的bgsave命令] 都创建了子进程

如果重写很耗时就会阻塞主进程,所以重写过程是由后台子进程bgrewriteof来完成的,有以下好处:

  • 子进程进行 AOF 重写期间,主进程可以继续处理命令请求,从而避免阻塞主进程
  • 主进程 fork 子进程时,操作系统会把主进程的「页表」复制给子进程,页表记录的是虚拟地址和物理地址之间的映射关系,子进程可以通过页表找到对应的物理空间,父子进程共享物理内存,页表对应的页表项的属性会标记该物理内存的权限为只读

当父进程或子进程向这个内存发起写操作时,CPU 就会触发写保护中断,然后操作系统去复制物理内存,将父子进程的内存读写权限设置为可读写,最后才会对内存进行写操作,这个过程被称为「写时复制(Copy On Write)」,父子进程都有独立的数据副本,不需要通过加锁来保护数据

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

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

RDB

快照,顾名思义,就像相机一样,捕捉到某一瞬间的风景。

RDB快照也一样的,他记录的是某一时刻的内存数据,所以我们在使用RDB进行数据恢复时,只需要将RDB文件读入内存就可以了。另外,RDB快照是“全量快照”,也就是每次都会把内存中的所有数据记录到磁盘中,这是一个比较重的操作,如果执行得太频繁,会对 Redis 的性能产生影响。

而AOF,它记录的是每一条写命令,用AOF进行数据恢复,要先读取命令再执行。并且可以以秒级的方式记录操作命令。

这样比较起来,RDB的恢复速度更快一些,但是实时性不如AOF。


快照怎么用?

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

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

RDB的bgsave命令创建了子进程,子进程会复制父进程的页表。

页表记录的是虚拟地址和物理地址之间的映射关系,子进程可以通过页表找到对应的物理空间,父子进程共享物理内存。

如果父进程对内存数据是只读操作,那么父子进程互不影响;

但是,如果父进程想要修改共享数据,就会发生写时复制,父进程会复制一份物理内存,在这个副本上进行修改操作;而子进程会把原来的数据写入到RDB文件。

所以,父子进程的内存数据是分离的,父进程刚刚修改的数据,没办法在同一时间写入RDB文件

(父进程:内存数据的副本,子进程:原有的内存数据)

所以就会出现一些极端情况:

  • 假如系统刚好在RDB文件创建完毕之后崩溃了,那么Redis将会丢失父进程修改的数据
  • 如果所有的共享内存都被修改,那么就会触发写时复制,此时占用的内存是原来的2倍。 

所以,针对写操作比较多的场景,我们要留意一下快照过程中的内存变化,防止内存被占满


混合持久化

混合持久化,即【RDB的全量数据】 + 【AOF的增量数据】

这样的好处在于:

  1. 重启 Redis 加载数据时,前半部分是 RDB 内容,加载的时候速度会很快
  2. 加载完 RDB 的内容后,再加载后半部分的 AOF 内容,这里加载的是在子进程重写 AOF 期间,主线程处理的操作命令,让丢失的数据更少

(是一种基于 [恢复速度] 和 [数据丢失] 的折中与平衡)


番外:大key问题

大key造成的影响

  1. 客户端超时阻塞:Redis 执行命令是单线程,操作大key比较耗时,会阻塞Redis;从客户端这一视角看,就是很久很久都没有响应
  2. 网络阻塞:每次获取大 key 产生的网络流量很大
  3. 阻塞工作线程:如果使用 del 删除大 key 时,会阻塞工作线程,没办法处理后续的命令
  4. 内存分布不均:集群模型在 slot 分片不均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS 也会比较大
  5. 持久化相关的阻塞

大key对持久化的影响

对AOF日志的影响:

  • Always 策略:如果写入的是一个大key,主线程在执行fsync()函数时,阻塞的时间较久
  • Everysec 策略:由于是异步执行 fsync() 函数,所以大 Key 持久化的过程并不会影响主线程
  • No策略:永不执行 fsync() 函数,所以大 Key 持久化的过程也不会影响主线程

对AOF重写/RDB的bgsave命令的影响:

AOF 重写机制和 RDB 快照(bgsave 命令)的过程,都会通过 fork() 函数创建一个子进程来进行持久化:

  • 操作系统会把父进程的页表复制给子进程,父子进程可以共享内存数据,权限为只读;
  • 如果父进程或子进程想要修改共享内存、进行写操作时,操作系统会复制物理内存,将父子进程的内存读写权限设置为可读写,然后进行写操作,(这个过程就是写时复制)

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

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

(而大key占用物理内存是比较大的,所以在复制物理内存这个步骤,是比较耗时的,父进程阻塞比较久)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值