Redis原理之持久化

上篇文章:

Redis客户端使用(Client、Java、SpringBoot)https://blog.csdn.net/sniper_fandc/article/details/149140091?fromshare=blogdetail&sharetype=blogdetail&sharerId=149140091&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

目录

[1 持久化策略](#1 持久化策略)

[2 RDB](#2 RDB)

[2.1 手动触发](#2.1 手动触发)

[2.2 自动触发](#2.2 自动触发)

[2.2.1 配置文件配置](#2.2.1 配置文件配置)

[2.2.2 shutdown命令](#2.2.2 shutdown命令)

[2.2.3 主从复制](#2.2.3 主从复制)

[2.3 RDB优缺点](#2.3 RDB优缺点)

[3 AOF](#3 AOF)

[3.1 使用AOF](#3.1 使用AOF)

[3.2 AOF写入流程](#3.2 AOF写入流程)

[3.3 AOF重写(rewrite)流程](#3.3 AOF重写(rewrite)流程)

[3.3.1 手动触发](#3.3.1 手动触发)

[3.3.2 自动触发](#3.3.2 自动触发)


1 持久化策略

Redis为了防止因为进程退出导致的数据丢失,会把数据持久化到硬盘作为备份,下次重启时就可以从硬盘读取恢复数据。常见的备份策略有两个:定期备份和实时备份,对应的Redis持久化的策略也有两个:RDB(Redis DataBase)和AOF(Append Only File),RDB对应定期备份,AOF对应实时备份。

2 RDB

RDB就是定期把Redis内存中的数据生成一个"快照"(某一时刻的数据状态),写入硬盘中。Redis在启动时,会读取RDB文件,并把数据恢复到内存中。如果RDB文件损坏,就可能导致Redis启动失败,也可能Redis会启动成功,但是数据是异常的。

定期到底是什么时候,具体来讲有两种触发方式:

2.1 手动触发

用户手动调用命令来触发快照生成,命令有save和bgsave两个。save命令执行时,Redis不会执行其他命令,而是只进行快照生成和持久化(这里没有文件替换的过程,直接是打开RDB文件进行写入)。因此save命令可能会阻塞Redis服务器,不建议使用。

bgsave命令(bg表示background)使用多进程来实现并发编程模型,执行流程如下:

注意:并发编程模型不一定是由多线程实现,也可以使用多进程实现。

1.父进程接收到bgsave命令后,首先判断是否有其他子进程正在运行,比如已经有子进程正在执行RDB文件的生成。如果有,就命令返回。

2.如果没有,父进程执行fork系统调用(Linux提供的创建子进程的API),创建一个子进程。

注意:fork创建子进程是直接把父进程复制一份作为子进程。这里复制的内容主要有进程PCB、虚拟地址空间(内存中的数据)、文件描述符表等等,由于父进程和子进程中内存中的数据一模一样,且打开的文件描述符表也一样,因此子进程执行持久化的效果和父进程执行持久化的效果就一样。

并且fork在执行复制操作时,采用"写时拷贝"机制,即不是把所有数据都拷贝,而是先共用一份内存地址空间(即共享同一份数据),当某一方对某些数据进行修改时,才会触发对该数据的真正的拷贝。短时间内,父进程不会有大量数据修改,因此如果内存数据很多,使用fork的效率也会比较高。

3.父进程创建子进程后,继续响应其他命令,进而也不会阻塞Redis服务器。

4.子进程负责RDB文件的生成,直到持久化的完成。

5.子进程持久化完成后,发送信号给父进程通知,父进程更新统计信息,然后子进程就可以结束销毁。

关于步骤4,有一些细节需要关注:

**保存路径:**RDB文件的默认存储在Redis的工作目录下(可以通过配置文件配置路径,在dir属性名后配置路径)。RDB生成的镜像文件默认采用压缩的方式存储(方便网络传输和存储),是二进制文件,默认名dump.rdb。

**保存流程:**先把要持久化的数据保存到临时文件中,待所有数据保存完成后,删除旧的dump.rdb快照,把临时文件名修改为dump.rdb,保证快照文件始终只有一份。

**校验:**Redis默认开启了RDB机制,如果Redis启动时加载到损坏的RDB文件会拒绝启动。可以使用Redis提供的redis-check-rdb*工具检测RDB文件并获取对应的错误报告。

2.2 自动触发

自动触发有3种情况:

2.2.1 配置文件配置

在Redis的配置文件中,可以配置让Redis每隔多长时间或每修改多少次就触发持久化。

具体在配置文件的:save <seconds> <changes>,seconds表示秒级时间,changes表示修改次数,即在seconds时间内,如果修改次数超过changes,seconds时间过后 就自动触发RDB文件的生成。不过这也可能带来RDB文件和Redis内存中数据不一致的情况,比如在下一次快照的持久化时间前,Redis进行了大量key的修改,但是此时没有过seconds的时间,恰好Redis服务器崩溃,再次重启Redis服务器时读取的还是旧的快照,即这期间的key的修改的数据丢失了。。

注意:虽然可以灵活配置,但是生成RDB文件的成本还是比较高,因此不能让自动触发的操作太频繁。可以配置save ""来关闭自动触发。

2.2.2 shutdown命令

通过shutdown命令主动正常关闭Redis服务器,Redis服务器也会自动触发RDB文件的生成。如果在Linux中使用service redis-server restart命令(重启redist服务器),背后也是调用shutdown命令,属于正常关闭的方式,因此也会自动触发持久化。

注意:如果异常关闭Redis服务器,就不会自动触发。比如使用kill命令杀死Redis进程,或者服务器崩溃,或者服务器所在主机重启。这种情况下,就有可能导致数据丢失。

2.2.3 主从复制

Redis进行主从复制时,主节点会自动生成RDB快照文件,然后通过网络传输发送给从节点。

2.3 RDB优缺点

1.RDB是个压缩的二进制文件,适用于备份、全量复制等场景。

2.Redis加载RDB恢复数据远远快于AOF(二进制文件,体积小,读写速度快)。

3.RDB方式数据没办法做到实时持久化。因为bgsave每次运行都要执行fork创建子进程,属于重量级操作,频繁执行成本过高。

4.RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个RDB版本,兼容性可能有风险。

3 AOF

AOF是实时的持久化机制,但是不是把数据持久化到硬盘中,而是把操作命令持久化到独立日志文件中,每次Redis重启时会读取该文件重新执行操作命令,从而实现数据恢复。

3.1 使用AOF

需要在Redis的配置文件中配置appendonly,no表示不开启(默认),yes表示开启。开启后,数据恢复加载的就是AOF文件。

appendfilename配置用于配置AOF文件名,默认是appendonly.aof。aof文件是文本文件,而不是二进制文件。

持久化工作目录也是dir配置。

注意:引入AOF不意味着对性能影响更大了(AOF要读写磁盘)。原因是因为1.AOF不是每次有一个操作需要写入就立即写入到磁盘的,而是先写入缓冲区,缓冲区累积一定数量的操作后再一次性写入磁盘(此时如果发生Redis异常终止,就会导致数据丢失),降低了磁盘IO次数。2.由于AOF是对一个文件进行续写,从磁盘角度来看是顺序写入(顺序访问),比随机访问的速度快。因此AOF实际对性能没有太大影响。

3.2 AOF写入流程

1.所有修改操作追加到AOF缓冲区中。

2.AOF缓冲区根据刷新策略,定期向磁盘中同步缓存的操作。

3.AOF越来越大,就定期触发rewrite重写,对aof文件进行压缩。

4.当启用AOF,Redis重启时,会把aof文件的操作重新执行一遍,数据也就载入Redis内存中。

对于步骤2,刷新策略有3种,均可在配置文件配置appendfsync的值:

|----------|--------------------------------------------------|
| 配置项 | 含义 |
| always | 操作写入缓冲区后立即调用fsync(系统调用)同步到磁盘中 |
| everysec | 操作写入缓冲区后不调用fsync,只进行write(也是系统调用),每秒由同步线程进行fsync |
| no | 操作写入缓冲区后只进行write,由操作系统控制fsync |

默认配置是everysec,频率适中,兼顾性能和数据安全。always虽然没有数据丢失,但是频率太高,性能低下。no虽然性能很高,但是数据安全无法保证。

注意:系统调用fsync和write的区别。write是延迟写,即会把数据写入Linux提供的页缓冲区,由Linux控制同步硬盘的系统调用机制,因此也会出现数据丢失问题。而fsync是强制写硬盘,会阻塞直到写入完成。

3.3 AOF重写(rewrite)流程

**为什么要重写?**答:让AOF文件占用体积变小,从而减少Redis启动时间。

**为什么会体积变小呢?**答:AOF文件的目的是可以通过操作恢复数据,只保留能恢复数据的操作就好了。但是文件可能存在冗余操作,比如多次修改同一个key的值,那么只保留最后一次修改操作就好了。重写可以删除(或合并)一些冗余操作,从而让文件更小。

AOF重写也有手动触发和自动触发两个时机:

3.3.1 手动触发

调用bgrewriteaof命令。

3.3.2 自动触发

配置文件配置auto-aof-rewrite-min-size和auto-aof-rewrite-percentage,auto-aof-rewrite-min-size表示触发重写时AOF文件的最小体积,默认64MB。auto-aof-rewrite-percentage表示触发重写时AOF文件大小相当于上次重写后文件增加的比例。

1.当执行AOF重写时,如果当前进程正在执行AOF重写,请求不执行。如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成之后再执行。

2.父进程调用fork创建子进程。

3.父进程调用fork后,如果有新的命令操作请求到来,就继续执行,并把操作命令写入到aof_buf缓冲区,同时也会写到aof_rewrite_buf缓冲区。这里aof_buf是旧AOF文件的缓冲区,而aof_rewrite_buf是子进程没有的数据的新AOF文件的缓冲区。

4.子进程在创建后执行重写,由于子进程已经有父进程fork前的数据了,而AOF文件最终目的是恢复数据,因此只需要把内存中的数据重写到新的AOF文件中。即新的AOF文件中的内容目前是文本类型的数据,而不是命令操作(重写命令操作还得在恢复数据时候重新执行命令操作,速度更慢了)。

注意:子进程的重写类似于RDB快照文件的生成,因此Redis为了速度,引入混合持久化(配置文件中配置项aof_use_rdb_preamble可以修改是否开启),即开启混合持久化后子进程重写新的AOF文件存储的是和RDB一样的二进制数据,而在后面aof_rewrite_buf缓冲区追加操作新的AOF文件存储的是文本形式的命令操作。

由于内存中的数据已经是所有最终操作命令后的数据,即没有冗余操作,因此写入内存中的数据就已经天然完成了删除和合并命令的操作。

5.子进程重写结束后,发送信号通知父进程。

6.父进程把aof_rewrite_buf缓冲区新增的命令操作追加到新的AOF文件中。

7.用新的AOF文件代替旧的AOF文件。

注意:在步骤3中,既然新的AOF文件最终会代替旧的AOF文件,为什么父进程还要根据原有的AOF写入流程把新来的命令操作写入旧的AOF文件?这其实是出于容错的考虑,因为如果在重写过程中,Redis发生异常崩溃,那么子进程的数据就会丢失,新的AOF文件还不完整,此时旧的AOF文件一直在正常写入,从而保证了极端情况下的数据安全。

下篇文章:

相关推荐
阿维的博客日记1 分钟前
Redis的旁路缓存策略和先删除缓存后更新数据库,先更新数据库后删除缓存,这三种策略之间有什么关系??
数据库·redis·缓存
ictI CABL17 分钟前
redis连接服务
数据库·redis·bootstrap
苍煜24 分钟前
SpringBoot单体应用到分布式下的数据库锁、事务、Redis事务、分布式锁、分布式事务协调
数据库·spring boot·分布式
风筝在晴天搁浅40 分钟前
设置一个带超时时间的LRU缓存
缓存
AI进化营-智能译站1 小时前
ROS2 C++开发系列18-STL容器实战:deque缓存激光雷达数据|priority_queue调度任务
开发语言·c++·缓存·ai
xmjd msup1 小时前
mysql的分区表
数据库·mysql
Lyyaoo.1 小时前
【JAVA Spring面经】Spring 事务失效情况
java·数据库·spring
MeAT ITEM1 小时前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
dovens1 小时前
PostgreSQL 中进行数据导入和导出
大数据·数据库·postgresql
IOT.FIVE.NO.11 小时前
claude code desktop cowork报错解决和记录Workspace..The isolated Linux environment ...
linux·服务器·数据库