迈入Redis:持久化

目录

前言

认识Redis的持久化

Redis实现持久化具体策略

RDB策略

手动触发

fork

写实拷贝

自动触发

RDB的rdb镜像文件

RDB的效果演示

第一种

第二种

第三种

第四种

第五种

RDB的优缺点

AOF的基本使用

缓冲区刷新策略

AOF重写机制

AOF重写流程

问题

混合持久化

信号的解释


前言

MySQL的四个比较核心的特性:

1.原子性

原子性确保事务中的所有操作要么全部成功,要么全部失败回滚,通过事务回滚等机制确保操作的完整性

2.一致性

一致性则是举个比较形象的例子,你出去外面买吃的,你给商家钱,商家自然就多一份钱,你就少一份钱,保持了平衡

3.持久性(和持久化说的是一回事)

4.隔离性

认识Redis的持久化

将数据存在硬盘上--->持久

将数据存在内存上--->不持久

换句话说,也就是你的进程和主机重启后,数据还存不存在的问题

而Redis是一个"内存"数据库,把数据存在内存中---->不持久

这也是Redis 相对于 MySQL这样的关系型数据库的优势---->效率高

所以在Redis中,既要效率又要持久化,所以Redis硬盘存一份数据,内存也存一份数据,而这两****份数据在理论是完全相同的,实际可能存在差异,但是取决于我们怎么去实现持久化

将一份数据同时写入硬盘和内存,然后通过内存来读取,硬盘中的数据也就只在Redis重启以后,用来恢复内存中的数据,也就是"备份",这样做的代价则是一份数据需要存储两次,消耗更多的空间,但是硬盘比较便宜,开销不会带来太多的成本

对于同时两边都写,但是实际怎么写硬盘还是有不同的策略,可以保证数据不丢失且整体效率高

Redis实现持久化具体策略

1.RDB(Redis DataBase)

定期备份

每过一段时间,将整体的文件进行备份盘中

2.AOF(Append Only File)

实时备份

每当出现一个新文件,立马想备份盘中拷贝备份

RDB策略

RDB定期 地把我们Redis内存中的数据,都给写入硬盘,形成一个**"快照**"

何为"快照"呢?顾名思义,可以理解成:警察在某个案发现场拉上警戒线后就开始拍照记录现场线索,后续也同样需要用这些照片来还原现场当作证据一般。RDB在这里也就是同样的原理,Redis给内存中当前存储的数据拍张照,生成文件,保存到硬盘中,后续一旦Redis重启了,内存中的数据没了,就可以根据当时拍照生成的文件来进行内存中数据的还原

这里说的"定期"具体来讲,又有两种方式:

手动触发

也就是通过Redis客户端,执行特定命令,来触发快照的生成

1.save

执行save的时候,Redis就会全力以赴地进行"快照生成"操作,此时就会阻塞Redis的其他客户端的命令,因为Redis是单线程的服务器,会导致类似于key*的后果

一般不建议使用save

2.bgsave

bgsave则是将"快照生成"的操作移至后面来进行执行,不会影响Redis服务器处理其他客户端的请求和命令

这里用到则是一种并发编程的场景(处理客户端请求的同时保持持久化保存数据),是通过"多进程"的方式来进行完成和实现的

客户端发出besave的命令上传给图中的"父进程"即Redis服务器,然后分为四种情况:

(1)判定当前是否已经存在其他正在工作的子进程

比如现在已经有一个子进程正在执行bgsave,此时就直接把当前的bgsave返回(因为持久化的数据为同一份,最终保存一个即可)

(2)如果没有其他的工作子进程,就通过fork这样的系统调用创建一个子进程

这里所谈到的fork是Linux系统提供的一个创建子进程的api(系统调用),如果是别的系统(比如widows),创建的就不是fork了

fork

fork创建子进程简单粗暴,直接把当前的父进程复制一份,复制PCB,虚拟地址空间(内存中的数据),文件描述符表,作为子进程,一旦完成复制,父子进程就是两个独立的进程,各自执行各自的了

本来Redis server中,有若干变量,保存了一些键值对数据,随着这样fork的进行,子进程的内存里也会存在和刚才父进程中相同的变量,因此接下来安排子进程去执行"持久化"操作,也就相当于把父进程本体这里的内存数据给持久化了,父进程打开一个文件,fork了之后,子进程也是可以同样使用这个文件的(因为父进程每次打开一个文件,都会在内存中包含有相应的文件描述符表被子进程所继承),即子进程也可以对对应的文件进行读写操作,也就导致了子进程持久化写入的那个文件和父进程本来要写的文件是同一个了

接下来我们就可以抛出一个问题:如果当前,Redis服务器中,存储的数据特别多,内存消耗特别大(如100GB),此时,进行上述的复制操作,是否会有很大的性能开销?

此时的性能开销,其实挺小的,fork在进行内存拷贝的时候,并不是简单无脑地直接把所有数据都拷贝一份,而是"写时拷贝"的机制来完成的

写实拷贝

如果子进程里的这个内存数据,和父进程里的内存数据是完全一样的,此时就不触发真正的拷贝动作,即子进程和父进程共享同一个内存

但是一旦父进程或者子进程一方产生改变(如键值对中的value发生改变,修改了某个键值对,删减了某个键值对),则触发了真正的物理内存上的数据拷贝

在进行bgsave这个场景中,绝大部分的内存数据,是不需要改变的(整体来说这个过程还是执行的比较快的,这个短时间内,父进程不会有大量的内存数据发生变化)

与此同时,子进程的"写时拷贝"并不会触发很多次,也就保证了整体的"拷贝时间的"是可控的,高效的

(3)子进程负责进行写文件,生成快照的过程;父进程继续接收客户端的请求,继续正常提供服务

(4)子进程完成整体的持久化过程后,就会通知父进程,干完了,父进程就会更新一些统计数据,子进程就可以结束销毁了

自动触发

在Redis配置文件中,设置一下,让Redis每隔多长时间/每产生多少次修改就触发

RDB的rdb镜像文件

redis生成的rdb文件,是存放在redis的工作目录中的,也是在redis配置文件中进行设置的

如下为rdb机制生成的镜像文件,redis服务器默认就是开启了rdb的

dump.rdb为二进制文件,就是把内存中的数据以压缩的形式保存到这个二进制文件中,需要消耗一定的cpu资源,但是能节省存储空间

如HTTP协议中的响应数据经常就会压缩

对于这个二进制文件,最多拿vim打开看看就行,不要乱改,一旦数据的格式被破坏了,就麻烦了

后续redis服务器重新启动,就会尝试加载这个rdb文件,如果发现格式错误,就可能会加载数据失败,对于这个rdb文件,虽然我们不主动去找他,他还是可能会出现一些意外问题

一旦通过一些操作(比如网络传输)引起这个文件被破坏,此时redis服务器就会无法启动

所有redis提供了rdb检查工具,如下图

rdb持久化操作,是可以触发很多次的,当执行生成rdb镜像操作的时候,此时就会把要生成的快照数据保存在一个临时文件中,当这个快照生成完毕之后,再删除之前的rdb文件,把新生成的临时的rdb文件名字改成刚才的dump.rdb

RDB的效果演示

打开redis.conf文件

rdb生成的持久化文件的名字,如下图

rdb文件中的数据,不是你这边插入了数据,就立刻更新的

我们启动redis客户端,插入几对键值对,没有运行手动触发的命令,也达不到自动触发的条件

我们回顾一下rdb触发条件:

1.手动(save,bgsave)

2.自动(配置文件中,进行设置)

如下为触发时机

此处的数值都可以修改,虽然此处的这些数值,都可以自由修改配置,但是,此处修改上述数据的时候,要有一个基本的原则

生成一次rdb快照,这个成本是比较高的,不能让这个操作太过于频繁,正因为rdb生成不能太过于频繁,这就导致了快照里的数据,和当时实时数据情况可能存在偏差

两次生成rdb之间的间隔最少是60s

比如:

12:00:00生成了rdb(硬盘上的快照数据和内存中一致)

12:00:01开始,redis收到了大量的key的变化请求

12:01:00生成下一个快照文件

在中间的时间段内,redis服务器挂了,此时,就会导致12:00:00后的数据全部丢失----->AOF就是解决这个问题的方案

第一种

手动执行save或者bgsave触发一次生成快照

由于这里的数据比较少,执行bgsave瞬间就完成了,立刻查看应该就是有结果的。如果以后我们接触的数据多了,执行bgsave就需要消耗一定的时间,立刻查看不一定就是生成完毕了

通过上述的操作就可以发现,redis服务器在重新启动的时候,加载了rdb文件的内容,恢复了内存中之前的状态

第二种

插入新的key,不手动执行bgsave

如果是通过正常流程重新启动redis服务器,此时redis服务器会在退出的时候,自动触发rdb操作,但是如果是异常重启(kill -9或者服务器掉电),此时redis来不及生成rdb,内存中尚未保存到快照中的数据,就会随着重启而丢失

刚刚没有执行bgsave,但是redis重启后,key4依然存在

ubuntu系统下kill一个进程后会触发守护进程生成一个新的进程(centos则不会)

redis生成快照操作,不仅仅是手动执行命令才触发,也可以自动触发

(1)通过刚才配置文件中save执行M时间内,修改N次

(2)通过shutdown命令关闭redis服务器也会触发(service redis-server restate)

(3)redis进行主从复制的时候,主节点会自动生成rdb快照,然后把rdb快照文件内容传输给从节点

第三种

bgsave的操作流程就是创建子进程,子进程完成持久化操作---->持久化速度太快了(数据少),难以观察到子进程

持久化会把数据写入到新的文件中,然后使用新的文件代替旧的文件(这个容易观察到)

如果直接使用save命令,此时是不会触发子进程&文件替换逻辑,如果是save就直接在当前进程中,往刚才同一个文件中写数据了

可以使用Linux的stat命令,查看文件的inode编号

如上所示,文件已经不是同一个文件了,只不过内容是一样的

inode编号相当于文件的身份标识

Linux文件操作系统

文件系统典型的组织方式(ext4)主要把整个文件系统分成了三个大的部分

1.超级块(放的是一些管理信息)

2.inode区(存放inode节点,每个文件都会分配一个inode数据结构,包含了文件的各种元数据)

3.block区,存放文件的数据内容

使用新的文件,替换旧的文件,有种"克隆人"的感觉

第四种

通过配置自动生成rdb快照(执行flushall也会清空rdb文件)

redis来说,配置文件修改之后,一定要重新启动服务器才能生效,当然,如果想立即生效,也可以通过命令的方式修改

关闭自动快照

第五种

如果把rdb文件故意怪坏了,会怎么样?

手动的把rdb文件内容改坏,然后一定是通过kill进程的方式,重新启动redis服务器,如果通过service redis-server restart重启,就会在redis服务器退出的时候,重新生成rdb快照,就把我们刚才改坏的文件给替换掉了

这里具体redis会怎样,取决于rdb文件坏了的地方在哪里,改坏的地方如果是在文件末尾,就对前面的内容没有什么影响,如果是中间位置就不一定了

当把中间的位置改坏了之后,发现redis服务器启动不了了

当redis服务器挂了的时候,可以看看redis日志,了解一下发生了什么

这个路径是在配置文件中设置的

上面的是日志文件本体

下面的是一个压缩包,如果这里的日志文件太大了,redis就会将这个日志文件进行压缩

在rdb恢复数据过程中出现了问题

rdb文件是二进制的,直接把坏了的rdb文件交给redis服务器去使用,得到的结果是不可预期的

可能redis服务器能够启动,但是得到的数据可能正确也可能有问题,也可能redis服务器直接启动失败

redis也提供了rdb文件的检查工具,可以通过检查工具,检查一下rdb文件格式是否符合要求

检查工具,和redis服务器在5.0版本是同一个可执行程序,可以在运行的时候加入不同的选项,从而使用其中不同的功能

运行的时候,加入rdb文件作为命令行的参数,此时就是以检查工具的方式来运行,不会真的启动redis服务器

RDB的优缺点

老版本的redis的rdb文件,放到新版本的redis中不一定能识别,一般来说,redis版本都是统一的,如果确实需要有一些"升级版本"的需求,确实需要升级,确实遇到了不兼容的问题,就可以通过写一个程序的方式,直接遍历旧的redis中所有的key,把数据取出来,插入到新的redis服务器中即可

RDB最大的问题,就是不能实时的持续化保存数据

在两次生成快照之后,实时的数据可能会随着重启而丢失

AOF的基本使用

append only file

类似于MySQL的binlog,就会把用户的每个操作,都记录到文件里,当redis重新启动的时候,就会读取AOF这个文件中的内容,用来恢复数据

AOF默认一般是关闭状态,修改配置文件,来开启AOF的功能

当开启aof的时候,rbd就不生效了,启动的时候不再读取rdb文件内容了

所在的位置,也是和rdb所在目录一样,/var/lib/redis(可配置的)

AOF是一个文本文件,每次进行的操作,都会被记录到文本文件中,通过一些特殊符号作为分隔符,来对命令的细节作出区分(具体的分隔符规则,不必深究)

Redis虽然是一个单线程的服务器,但是速度很快,重要原因只是操作内存,引入AOF之后,又要写内存,又要写硬盘,还能和之前一样快吗?

实际上是没有影响的,没有影响到redis处理请求的速度

1.AOF机制并非是直接让工作线程把数据写入硬盘,而是先写入一个内存中的缓冲区,积累一波之后,在同一写入硬盘

大大降低了写硬盘的次数

假设100个请求,一次写入硬盘比分100次写入硬盘要快的多

写硬盘的时候,写入硬盘数据的多少,对于性能影响没有很大,但是写入硬盘的次数则影响很大

2.硬盘上读取数据,顺序读取的速度还是比较快的(还是比内存慢很多),随机访问则速度更慢

AOF是每次把新的操作写入到原有文件的末尾,属于顺序写入

缓冲区刷新策略

如果把数据写入到缓冲区中,本质还是在内存中,在这个时候遇到进程挂了,或者主机掉电了,缓冲区里面的数据也随之丢失了,因为缓冲区中的数据还没来得及写入硬盘

对于这一类型问题,MySQL事务中的隔离级别也是如此,需要懂得取舍

redis给出了一些选项,可供我们选择,根据实际情况来决定这么取舍,缓冲区的刷新策略

刷新频率越高,性能影响越大,同时数据的可靠性越高

刷新频率越低,性能影响越小,同时数据可靠性越低

AOF重写机制

AOF文件持续增长,体积越来越大,会影响到redis下次启动的时间,因为redis启动时要读取AOF文件的内容

然而,上述AOF中的文件,有一些内容是冗余的

比如,有一个客户端,对redis进行下列操作

以上操作,记录了中间的过程,实际上redis在重新启动的时候,只是关注最终结果

因此redis就存在一个机制,能够针对AOF文件进行整理操作,这个操作就是能够剔除其中的冗余操作,并且合并一些操作,达到给AOF文件"瘦身"的效果

AOF重写流程

机制触发方式:

创建子进程,父进程仍然负责接收请求,子进程负责针对aof文件进行重写,重写的时候,不关心aof文件中原来都有啥,只是关心内存中最终的数据状态

子进程只需要把内存中当前的数据,获取出来,以AOF的格式写入到一个新的AOF文件中(内存中的数据的状态,就已经相当于是把AOF文件结果整理后的模样了)

此处子进程写数据的过程,非常类似于RDB生成一个镜像快照,只不过RDB这里是按照二进制的方式来生成的,AOF重写,则是按照AOF这里要求的文本格式来要求的,但是最终目的都是为了把当前内存中的所有数据状态记录到文件中

子进程写新的aof文件的同时,父进程仍然在不停的接收客户端新的请求,父进程还是会写把这些请求产生的AOF数据先写入到缓冲区,再刷新到原有的AOF文件里

在创建子进程的一瞬间,子进程就继承了当前父进程的内存状态

因此,子进程里的内存数据是父进程fork之前的状态,fork之后,新来的请求,对内存造成的修改,是子进程不知道的

此时,父进程这里又准备了一个aof_rewrite_buf缓冲区,专门放fork之后收到的数据

子进程这边,把aof数据写完之后,会通过信号通知一下父进程,父进程再把aof_rewrite_buf缓冲区中的内容也写到新的AOF文件中去

就可以用的AOF文件替代旧的AOF文件了

问题

通过上面的步骤梳理,我们同时也会产生以下疑问:

如果在执行bgrewriteaof的时候,当前redis正在进行aof重写了,会怎么样?

此时不会再次执行aof重写,直接返回

如果在执行bgrewriteaof的时候,发现当前redis在生成rdb文件的快照,会怎么样?

此时,aof重写操作就会等待,等待rdb快照生成完毕之后,再进行执行aof重写

rdb对于fork之后的新数据,直接置之不理;aof对于fork之后的新数据,采取了aof_rewrite_buf缓冲区的方法来处理

rdb本身的设计理念,就是用来"定时备份"的,只要是定期备份,就难以和最新的数据保持一致

aof的设计理念则是实时备份

实时备份不一定就比定期备份更好,还是要看实际场景的

现在的系统中,系统资源一般都是比较充裕的

aof的开销也不算事,一般来说,aof的使用场景更多一些

父进程fork完毕之后,就已经让子进程写新的aof文件了;并且随着时间的推移,子进程很快就写完了新的文件,要让新的aof文件代替旧的文件

那父进程此时还在继续写这个即将消亡的旧的aof文件是否还有意义?(不能不写)

因为我们要考虑到极端的情况:假设在重写过程中,重写了一半,服务器挂了,子进程内存的数据就会丢失,新的aof文件内容还不完整

所有如果父进程不坚持写旧的aof文件,重启就没办法保证数据的完整性了

混合持久化

AOF本来是按照文本的方式来写入文件的,但是文本的方式写文件,后续加载的成本是比较高的

对此redis引入了"混合持久化"的方式,结合了rdb和aof的特点

这个选项默认都为yes表示开启混合持久化

按照aof的方式,每一个请求/操作,都被记录入文件,在触发aof重写之后,就会把当前内存的状态按照rdb的二进制格式写入到新的aof文件中

后续再进行的操作,仍然是按照aof文本的方式追加到文件后面

信号的解释

当redis同时存在aof文件和rdb快照的时候,此时以谁为主?

以aof为主,rdb直接被忽略

因为AOF中包含的数据比RDB更全

接下来对于信号这个东西呢,我们来展开一个解释

信号可以理解为Linux的神经系统

进程之间的相互作用(也可以视为是进程间通信的一种手段)

信号能表达的信息有限,并非像socket这样的方式可以传输任意的数据

因此,像上述父子进程场景中,子进程表达"我干完了",这种简单的信息传递,使用信号也是ok,当然,使用其他进程间通信方式也是ok的

在另一层理解中,信号也可以理解为Linux内核版本的事件机制,主要包含:信号源(不太重要,更关注的是信号发给谁),信号的类型,信号的处理函数

相关推荐
m0_743623922 小时前
golang如何使用iota常量生成器_golang iota常量生成器使用教程
jvm·数据库·python
baidu_340998822 小时前
mysql如何排查连接数爆满原因_mysql show processlist分析
jvm·数据库·python
baidu_340998822 小时前
如何用HTML函数工具测试显卡性能_基准跑分详解【详解】
jvm·数据库·python
qq_283720052 小时前
Chroma 向量数据库详细介绍与实战全攻略
数据库·人工智能·向量数据库·chroma
瀚高PG实验室2 小时前
pg_dump: error: no matching tables were found
数据库·瀚高数据库
2301_813599552 小时前
如何处理MongoDB副本集中节点IP变更_rs.reconfig强制更新配置矩阵
jvm·数据库·python
健康平安的活着2 小时前
mysql中不同时间类型(date/datetime/timestamp)的查询案例
数据库·mysql
User_芊芊君子2 小时前
数据库选型指南:架构演进的技术实践
大数据·数据库·架构