Bootstrap

Redis --- 第六讲 --- 关于持久化

前言

持久化:MySQL的事务,有四大比较核心的特性

1、原子性

2、一致性

3、持久性 =》 把数据存储到硬盘上 =》持久,把数据存储在内存上=》持久化。重启进程/重启主机之后,数据是否存在。

4、隔离性

Redis是一个内存数据库,把数据存储在内存中的!

内存中的数据是不持久的。要想能够做到持久,就需要让redis把数据存储到硬盘上。为了保证速度快,数据肯定还是得在内存中,但是为了持久,数据还得想办法存储在硬盘上。Redis决定,内存中也存数据,硬盘上也存数据。这样的两份数据,理论上是完全相同的。实际上可能存在一个小的概率有差异。取决于咱们具体怎么进行持久化。当要插入一个新的数据的时候,就需要把这个数据,同时写入到内存和硬盘,当查询某个数据的时候,直接从内存中读取。硬盘的数据只是在redis重启的时候,用来恢复内存中的数据。代价就是消耗了更多的空间,同一份数据,存储了两遍。说是两边写,但是实际上具体怎么写硬盘还有不同的策略,可以保证整体的效率足够高。

redis实现持久化的时候,具体是按照什么样的策略来实现的呢?

1、RDB =》Redis DataBase,按照定期的方式进行数据持久化。

2、AOF =》 Append Only File,按照实时的方式进行数据持久化。

一、RDB持久化

1、RDB持久化的触发时机

RDB定期的把我们Redis中的所有数据,都给写入硬盘中,生成一个快照。Redis给内存中当前存储的这些数据,拍个照片,生成一个文件,存储在硬盘中,后续Redis一旦重启了,就可以根据刚才的快照把内存中的数据给恢复回来。

定期:具体来说,又有两种方式。

1)手动触发。程序员通过redis客户端,执行特定的命令,来触发快照的生成

save 执行save的时候,redis就会全力以赴的进行快照生成的操作,此时就会阻塞redis的其他客户端的命令。导致类似于keys* 的后果。一般不建议使用save。

bgsave bg=》background后面,不会影响redis服务器处理其他客户端的请求和命令。redis咋做到的?是不是偷摸搞了个多线程?并非如此!并发编程的场景,此处redis使用的是多进程的方式,来完成并发编程,来完成bgsave的实现。

bgsave的执行流程:

父进程就是redis服务器。

第一步:判定当前是否已经存在其他正在工作的子进程。比如现在已经有一个子进程正在进行执行bgsave,此时就直接把当前的bgsave返回。

第二步:如果没有其他的工作子进程,就通过fork这样的系统调用,创建出一个子进程来。 子进程会继承父进程所有的信息。因此,复制出来的这个子进程的内存中的数据就是和父进程是一样的。接下来安排子进程去执行持久化操作,也就相当于把父进程本体这里的内存数据给持久化了。父进程打开了一个文件,fork之后,子进程也是可以同样使用这个文件的。也就导致了子进程持久化写入的那个文件就是和父进程打开的那个文件相同。

如果当前,redis服务器中存储的数据特别多,内存消耗特别大。此时,进行上述的复制操作。是否有很大的开销?

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

第三步:子进程负责写文件,生成快照的过程,父进程继续接收客户端的请求,继续正常提供服务。

第四步:子进程完成整体的持久化过程之后,就会使用信号通知父进程,父进程就会更新一些统计信息。子进程就可以结束销毁了。

RDB的rdb文件

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

可以打开这个目录,可以看到dump.rdb文件,这是rdb机制生成的镜像文件,redis服务器默认就是开启了rdb的。二进制的文件,把内存中的数据,以压缩的形式,保存到这个二进制文件中。压缩:需要消耗一定的cpu资源,但是能节省存储空间。

redis提供了rdb文件的检查工具,来验证rdb文件的格式。

当执行生成rdb镜像操作的时候,此时就会把要生成的快照数据,先保存到一个临时文件中,当这个快照生成完毕之后,再删除之前的rdb文件,把新生成的临时的rdb文件名字改成刚才的dump.rdb自始至终rdb文件只有一个。

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

格式:

自动触发的设置:

虽然此处的这些数值,都可以自由修改设置。但是,此处修改上述数据的时候,要有一个基本的原则。生成一次rdb快照,这个成本是一个比较高的成本,不能让这个操作执行的太频繁。正因为rdb生成的不能太频繁,这就导致,快照里的数据和当前实时的数据情况,可能存在偏差。AOF就是解决这个问题的优化方案。

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

redis生成快照操作,不仅仅是手动执行命令才触发,也可以自动触发。通过刚才配置文件中,save执行M时间内,修改N次。通过shutdown命令关闭redis服务器也会触发。redis进行主从复制的时候,主节点也会自动生成rdb快照,然后rdb快照文件内容传输给从节点。

实际开发中,更害怕的是出现异常情况。

设置key5,kill命令杀掉redis服务器,key5丢失。

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

bgsave操作流程是创建子进程,子进程完成持久化操作,持久化会把数据写入到新的文件中,然后使用新的文件替换旧的文件。可以使用linux命令的stat命令来查看inode编号

文件不再是同一个文件了,只不过内容是一样的。inode编号,就相当于文件的身份标识。Linux文件系统:文件系统典型的组织方式ext4主要是把整个文件系统分成了三个大的部分。

1、超级块。2、inode区,存放inode节点,每个文件都会分配一个inode数据结构,包含了文件的各种元数据。3、block区存放文件的数据内容。

使用新的文件,替换旧的文件。

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

通过配置自动生成rdb快照
redis来说,配置文件修改之后,一定要重新启动服务器,才能生效。

如果把rdb文件搞坏了会怎么样?

手动的把rdb文件内容改坏,然后一定是通过kill继承的方式,重新启动redis服务器。但是看起来好像redis服务器没有收到啥影响,还是能正常启动,还是能正确获取到key,如果中间位置坏了可就不一定了。当我们把中间位置改坏了之后,发现redis服务器启动不了了。我们可以查看redis日志,去发现redis发生了什么?

在rdb恢复数据的时候出现问题了。rdb文件时二进制的。直接就把坏了的rdb文件交给redis服务器去使用,得到的结果时不可预期的。可能redis服务器能启动,但是得到的数据可能正确也可能有问题。也可能redis服务器直接启动失败。

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

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

总结:

RDB使用二进制的方式来组织数据,AOF时使用文本的方式来组织数据的。则需要进行一系列的字符串切分操作。

老版本的redis的rdb文件,放到新版本的redis中不一定能识别,一般来说,实际工作中,redis版本都是统一的。如果确实需要升级,确实遇到了不兼容的问题。就可以通过写一个程序的方式,直接遍历旧的redis中的所有key,把数据取出来,插入到新的redis服务器中即可。RDB最大的问题,不能实时的持久化保存数据,在两次生成快照之间,实时的数据可能会随着重启而丢失,我们可以使用实时持久化(AOF)来解决这一问题。

二、AOF持久化

AOF的基本使用:

AOF :append only file类似于mysql的binlog,就会把用户的每个操作,都记录到文件中。当redis重新启动的时候,就会读取这个aof文件中的内容,用来恢复数据。当开启AOF的时候,RDB就不生效了。启动的时候就不再读取rdb文件内容了。

aof默认是关闭状态,我们需要通过配置文件打开它。

所在的位置,和RDB文件是相同的。所在的目录可以手动配置。

AOF是一个文本文件,每次进行操作的时候,都会被记录到文本文件中,通过一些特殊符号作为分隔符,来对命令的细节做出区分。

AOF是否会影响到redis的性能

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

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

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

大大降低了写硬盘的次数。

2、硬盘上读写数据,顺序读写的速度是比较快的,随机访问则速度是比较慢的。AOF是每次把新的操作写入到原有文件末尾,属于顺序写入。

AOF缓存区刷新策略

如果把数据写入到缓冲区里,本质还是在内存中,万一这个时候,突然进程挂了,或者主机掉电了,缓存区中没来得及写入硬盘得数据是会丢的。

redis给出了一些选项,让程序员,根据实际情况来决定怎么取舍,缓冲区的刷新策略。刷新频率越高,性能影响就越大,同时数据的可靠性就越高。刷新频率越低,性能影响越小,数据的可靠性就越低。

appendsync

always 频率最高的,数据可靠性最高,性能最低。

everysec 频率低一些,数据可靠性降低,性能会提高。

no 频率最低,数据可靠性最低,性能是最高的。

AOF的重写机制

AOF文件持续增长,体积越来越大。会影响到,redis下次启动的时间,redis启动的时候,要读取aof文件的内容。注意!上述AOF中的文件,有一些内容是冗余的。

AOF文件的内容,记录了中间的过程,实际上redis在重新启动的时候,只是关注最终结果。因此redis就存在一个机制,能够针对aof文件进行整理操作,这个整理就是能够提出其中的冗余操作,并且合并一些操作,达到给AOF文件瘦身这样的效果。

AOF重写机制的触发机制

手动触发:调用bgrewriteaof命令。

自动触发:

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的时候当前已经正在进行aof重写了,此时,不会再次执行aof重写,直接返回了。如果在执行bgwriteaof的时候,发现当前redis在生成rdb文件的快照,此时aof重写操作就会等待,等待rdb快照生成完毕之后,再进行执行aof重写。

rdb对于fork之后的新数据,就直接置之不理了,aof则对于fork之后的新数据,采取了aof_rewrite_buf缓冲区的方式来处理。rdb本身的设计理念,就是用来定期备份的。只要是定期备份,就难以和最新的数据保持一致。aof的理念则是实时备份,实时备份不一定就比定期备份更好,还是要看实际场景的。现在的系统中,系统的资源一般都是比较充裕的。aof的开销也不算事情,一般来说aof的适用场景更多一些。

当前父进程fork完毕之后,就已经让子进程写新的aof文件了,并且随着时间的推移,子进程很快就写完了新的文件,要让新的aof文件代替旧的aof文件,父进程此时还在继续写这个即将消亡的旧的aof文件是否还有意义?这是有意义的。不能不写,考虑到极端情况,假设在重写过程中,服务器掉电了。服务器挂了,此时子进程内部的数据就会丢失,新的aof文件内容还不完整,所以如果父进程不坚持写旧的aof文件,重启就没法保证数据的完整性了。

混合持久化

AOF本来是按照文本的方式来写入文件的,但是文本的方式写文件,后续加载的成本是比较高的。redis就引入了混合持久化的方式,结合了rdb和aof的特点。按照aof的方式,每一次请求/操作,都记录到文件中,在触发aof重写之后,就会把当前内存状态按照rdb的二进制格式写入到新的aof文件中。后续再进行的操作,仍然是按照文本的方式追加到AOF文件中。

开启混合持久化。

当redis上同时存在aof文件和rdb快照的时候,此时以aof为主,rdb就被忽略了。

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

信号通知父进程:信号这个东西,可以认为是Linux的神经系统。进程之间的相互作用(也可以视为是进程间通信的一种手段)。但是java生态中并不鼓励使用多进程模型编程,网络通信的场景除外。

;