【Redis】持久化

对于Redis中间件来说,一般是作为内存型数据库或者缓存出现的。但是由于其数据是在内存中,因此当Redis所在的主机宕机之后,数据就会消失,再次重启之后,没有任何数据。这对于生产环境来说,是属于P0级别的灾难了。

因此,对于Redis来说,不仅要将数据存在内存中,还要将数据持久化到硬盘上。这样当Redis重启之后,可以通过硬盘中的数据来恢复内存中的数据。如果Redis是作为内存数据库的话,那么客户端就可以继续进行访问得到数据;如果Redis是作为缓存的话,那么就可以减少MySQL的压力,从而避免MySQL宕机。

Redis有两种持久化方式:RDBAOF

 RDB

RDB是定期备份的持久化方式,即Redis定期会把内存中的数据进行备份,生成一个快照,保存在硬盘中。这样,当Redis重启的时候,就可以读取RDB文件来恢复内存数据。

触发方式

手动触发

程序猿在客户端输入特定的命令,来触发Redis服务器快照的生成。

save:通过save命令,可以使Redis服务器触发快照行为,进行备份

bgsave:通过bgsave命令,同样可以使Redis触发快照行为,进行备份。

对于save和bgsave两个命令来说,最大的区别就是save会和其他客户端命令一样,在同一个进程中执行,因此就会影响其他客户端命令的执行;而对于bgsave命令来说,并不会在当前进程中执行,而是fork一个子进程,在子进程中执行,这样就不会影响其他客户端的请求。

 bgsave的执行流程

f38d4f7ba88551f981b4f6b28fcd6a34.png

1.  客户端发送bgsave命令之后,Redis服务器会判断是否已经正在执行该命令。如果正在执行,那么就会直接进行返回。

2. 如果没有执行,那么Redis服务器就会通过fork命令,来创建出一个子进程。

fork命令是Linux系统提供的一个创建子进程的API。简单来说,fork命令就是将原来的进程给简单粗暴的复制一份。复制完成之后,两个进程之间就没有任何的关系,各自会执行各自的内容,不过由于后续的进程,即子进程是父进程复制而来,因此两个进程中的数据是完成相同的。这样,让子进程生成一份快照,就相当于让父进程生成一份快照。

对于fork命令来说,是不是有比较大的性能开销?不会,执行fork命令进行内存拷贝的时候,并不是在内存空间中把所有的数据全部复制一份,而是采用写时拷贝的方式来完成。

所谓写时拷贝,当父子进程的数据是完成相同时,两者指向的内存空间还是同样的位置。但是当任一进程发生改变时,才会开辟一个新的内存空间来记录变化的数据。

综上所述,使用fork命令生成父子进程时,并不会产生较大的性能开销。毕竟Redis改变的数据并不是大多数,而是一小部分,因此对于写时拷贝来说消耗并不是很大,整体来说较快。

3. 父进程继续执行客户端的其他命令请求,而子进程则是来生成快照,进行备份。

4. 子进程持久化完成之后,就会来通知父进程。父进程再更新一些统计信息之后,子进程就可以销毁了。

自动触发

1. 在Redis的配置文件中可以进行配置,以来达到自动触发定期快照的操作;

2. 在主从模式中,给主节点加从节点时,主节点会主动生成快照,发送给从节点;

3. 当客户端发送shutdown命令,进程关闭服务器时,会触发快照的生成,但是如果是意外宕机,例如掉电或者kill,那么此时就不会生成快照。

RDB文件

1. RDB文件的存放位置,是可以通过Redis的配置信息来设置的,默认是放在/var/lib/redis中。

261d3dc54f8b4842b16bf95c874176b3.png

2. Redis文件是一个二进制的文件。即把内存中的数据通过压缩的形式,保存到这个二进制文件中。通过压缩,可以节省空间,但是进行操作时就会消耗一定的CPU资源。

3. Redis重启时,就会通过RDB文件来恢复内存中的数据。但是如果RDB的数据内容发生了变化,那么就有可能重启失败,也有可能重启成功。因此,Redis还专门提供了检查RDB文件的工具,可以在重启服务器之前,先检查RDB文件是否发生错误。

4. 在快照生成RDB文件时,会将生成的这些内容存储到一个临时的文件中。当快照生成完毕,就会使用新生成的RDB文件来代替旧的RDB文件,这样硬盘中自始至终就只有一个文件了。

 5. 在bgsave命令的执行流程中,虽然fork子进程的性能消耗并不大,但是RDB文件的生成也有一个较高的性能开销,因此RDB文件不是实时备份,而是定期备份。正是由于定期备份,因此在一次备份完成之后,Redis宕机,再次重启时,备份之后操作的数据都不复存在。

RDB特点

1. RDB文件是一个二进制格式的文件,因此非常适合用于备份、全量复制等场景。

2. Redis加载RDB恢复数据往往比加载AOF恢复数据要快。这是因为RDB使用二进制来组织数据,而AOF则是使用文本格式来组织数据,而计算机天然对二进制好感度高。

3. Redis无法做到实时持久化,毕竟生成一次快照消耗的资源还是较多的,属于重量级操作,频繁执行成本过高。

4. RDB使用二进制格式保存,但是Redis不同的版本可能二进制格式略有不同,因此可能存在兼容性问题(例如将5版本的RDB放到7版本的RDB文件中,就可能运行不起来)。

AOF

AOF(Append Only File)是实时备份的持久化方式。所谓实时备份,并不是将Redis服务器内存中的数据进行持久化,而是把用户进行的操作当作日志记录成文件。也就是说,当用户执行某条操作时,Redis就会进行记录,并且写入到文件中。这样,当Redis服务器重新启动的时候,就回去读取AOF文件,用来恢复数据。

对于RDB方式的持久化来说,最大的问题是不能实时持久化来保存数据。这就导致在某些突发情况发生后,Redis服务器重启时,Redis内存中的数据可能会存在丢失的情况。

因此,AOF方式的持久化,就是用来解决RDB存在的问题。当开启AOF方式的持久化之后,Redis服务器重新启动时,就会读取AOF文件中的内容来恢复内存数据,不会再读取RDB文件的内容。

 开启AOF

默认情况下,Redis服务器是使用RDB的方式来进行持久化的。对于AOF来说,默认则是关闭状态,要想使用AOF,就要在配置文件中进行开启:

 当AOF方式的持久化开启之后,RDB就会失效。并且对于AOF来说,其文件生成的位置和RDB文件生成的位置在同一目录下。

AOF与Redis性能间的关系

引入AOF之后,既要操作内存(Redis服务器需要对客户端发送的请求进行响应),又要操作硬盘(Redis服务器需要把客户端的操作当成日志写入文件),这是否对Redis的效率产生了很大的影响?答案是没有。

AOF虽然是把客户端的操作给记录下来,但是并不是直接让工作线程把数据给写入硬盘。而是先把数据写入到内存中的缓冲区中,当积累一定数量之后(通过积累,可以降低写硬盘的次数,从而减少性能的消耗),统一写入硬盘(写入硬盘时,采用了顺序读取的方式,相对随机读取来说还是比较快的一种读取方式)。

通过上述描述,我相信大家存在一个疑惑:把数据写入缓冲区,那不还是在内存中,这些数据依然有丢失的风险,因此AOF依旧没解决RDB存在的问题。

其实不然,AOF的确解决了RDB存在的不能实时持久化的问题,这本质上存在一个可靠和性能的矛盾性。当数据特别可靠时,其性能一定会有所损耗;当性能非常好时,其数据可靠性就会有所下降。例如MySQL中的隔离级别,当隔离级别是可串行化时,其数据基本上完全可靠,但是性能低下;当隔离级别是读未提交时,其读取到的数据不一定可靠,但是其性能一定非常高。

综上,针对Redis的AOF来说,其官方也给出了不同的措施来解决不同业务场景下的问题,即程序员根据对系统的评估来取舍缓存区的刷新策略。刷新频率越高,那么其性能影响也就越大,但是其数据的可靠性就越高;刷新频率越低,性能影响就会越小,但是数据的可靠性就会降低。

在Redis中,使用appendfsync属性来配置刷新策略,并且给出了三个配置值。

可配置值 说明 性能与可靠性
always

客户端进行操作之后,数据进入缓存区之后,直接写入AOF文件中。

性能最低,可靠性最高。

everysec

客户端进行操作之后,先写入缓冲区,然后每秒写入一次文件。

性能适中,可靠性适中。

Redis默认此刷新策略。

no

客户端进行操作之后,先写入缓存区,然后根据操作系统来控制写入文件。

性能最高,可靠性最低。

重写机制

在AOF文件中,记录的是客户端每次进行的操作。但是在客户端操作的过程中,可能某个变量会重复被操作,最后被删除。这些操作如果记录在AOF文件中,那么在Redis服务器重启的时候,也会对这个变量进行反复操作,最后删除,那么就是消耗大量内存但是做了无用的事。

因此Redis就存在一个机制,能够对AOF文件进行整理操作,这个整理就能够剔除其中的冗余操作,并且合并一些操作,达到给AOF文件瘦身的效果。这样,当Reids服务器进行重启的时候,就不会机械性的做一些无用的操作。

这个机制就被称为Redis的重写机制。

触发方式

手动触发和自动触发两种。

手动触发使用bgrewriteaof命令进行操作;

自动触发则根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机,auto-aof-rewrite-min-size表示触发重写时AOF的最小文件大小,默认是64MB,auto-aof-rewrite-percentage代表当前AOF占用大小相比较上次重写时增加的比例。

重写流程

① 当Redis接收到了重写操作时,就会fork一个子进程。如果此时Redis服务器已经在重写,那么就不会再次执行命令,直接返回。

 ② 父进程仍然接收请求,子进程负责针对AOF文件进行重写。在重写的时候,并不关心AOF文件原来有啥内容,只关心此时内存中数据的最终状态。因此,子进程只需要把当前的数据获取出来,以AOF文件的格式写入到一个新的AOF文件中。

③ 子进程在重写AOF文件的同时,父进程还是会不停的接收客户端的请求,并且把这些请求产生的AOF数据写入到缓冲区中,再刷新到原来的AOF文件中。在执行这些命令的同时,父进程还会准备一个新的缓冲区,把fork之后产生的AOF数据放入其中。

为啥要准备这个新的缓冲区?

当fork之后,子进程中的内存数据是父进程fork之前的,因此对于fork之后的请求对内存造成的修改,子进程是不知道的。所以,当Redis把子进程的内存数据整理完毕之后,如果直接代替之前的文件,那么整理文件过程中产生的数据会丢失。所以,要准备这个缓冲区来接收fork之后的一些请求。

④ 子进程整理完毕之后,会通知父进程,父进程把新缓冲区中的内容加入到新AOF文件之中,就可以用新的文件来代替旧的AOF文件。

当在重写过程中,发现Redis正在备份。那么此时,AOF重写就会停止,等待RDB快照完成,再继续进行重写的操作。

混合持久化

对于RDB来说,是没法做到实时备份,存在数据丢失的风险;对于AOF来说,是使用文本的格式来写入数据,导致后续加载文件的成本较高。

由于RBD和AOF各有优缺点,因此就又有了混合持久化的方式。正所谓,小孩子才做选择,大人全都要。简单来说,混合持久化就是对于每一个操作,都会使用AOF的方式来写入文件;当执行重写操作时,就会把当前内存的状态按照RDB的格式写入到新的AOF文件中。这样,即可以保证所有数据的及时更新,又可以在读取文件时采用较小的成本。

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-16 08:02:03       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-16 08:02:03       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-16 08:02:03       58 阅读
  4. Python语言-面向对象

    2024-07-16 08:02:03       69 阅读

热门阅读

  1. AppML 案例:Products

    2024-07-16 08:02:03       22 阅读
  2. 深度学习--基础语法

    2024-07-16 08:02:03       18 阅读
  3. QT在PC开发中多串口通信有哪些方法

    2024-07-16 08:02:03       22 阅读
  4. 思科与华为交换机常用命令对比

    2024-07-16 08:02:03       21 阅读
  5. flutter ios打包 xcode报错module ‘xxx‘ not found

    2024-07-16 08:02:03       19 阅读
  6. 实验四:图像的锐化处理

    2024-07-16 08:02:03       25 阅读
  7. 共享库链接和加载时的路径搜索优先级

    2024-07-16 08:02:03       30 阅读