简介介绍Redis持久化
MySQL的事务,有四个比较核心的特性
- 原子性
- 一致性
- 持久性(持久化)
- 隔离性
- 数据存储在硬盘上->持久
- 数据存储在内存上->不持久
- 重启进程/重启主机,数据是否存在
- Redis是一个内存数据库,把数据存储在内存上
- 内存中的数据是不持久的,想要持久,就需要让redis把数据存储到硬盘上
Redis相比于MySQL这样的关系型数据库,最明显的特点/优势->效率高/快
- 为了保证速度快,数据还是得在内存中
- 为了保证持久性,数据想办法存储在硬盘上
redis决定,内存中也存数据,硬盘上也存数据;这样的两份数据,理论上完全相同,实际上可能存在一个小概率有差异,取决于咱们具体怎么进行持久化:当要插入一个新的数据的时候,就需要把这个数据,同时写入到内存和硬盘(具体怎么写还有不同的策略,可以保证整体效率还是很高),当查询某个数据的时候,直接从内存读取,硬盘的数据只是在redis重启的时候,用来恢复内存中的数据的
代价就是消耗了更多的空间,同一份数据,存储了两遍(硬盘比较便宜,开销不会带来太多的成本)
Redis实现持久化的时候,具体是按照什么样的策略来实现的呢?
- RDB => Redis DataBase
- AOF => Append Only File
在备份资料时,可以拿另外一块移动硬盘,来作为备份用的硬盘
硬盘(尤其是机械硬盘)是一台电脑上最容易坏的部分(CPU,显卡,内存坏的概率极低)
- RDB-定期备份:每个月,把电脑上的学习资料,整体的备份到这个备份盘中
- AOF-实时备份:只要我下载了一个新的学习资料,立即把这个学习资料往备份盘中拷贝一份
RDB的触发时机
RBD定期的把我们Redis内存中的所有数据,都给写入硬盘中,生成一个“快照”(Linux中提到过)
”定期“
- 手动进行触发:程序员通过redis客户端,执行特定的命令,来触发快照生成(save,bgsave)
- 自动触发:在redis配置文件中,设置一下,让redis每隔多长时间/每产生多少次修改,就出发
- save:执行save的时候,redis就会全力以赴的进行“快照生成”操作,此时就会阻塞redis的其他客户端的命令,导致类似于keys *的效果(一般不建议使用save)
- bgsave:bg-background(后面),不会影响redis服务器处理其他客户端的请求和命令,那么redis是否做了个多线程呢?并不是!这是一个并发编程的场景,不一定靠多线程,而是使用“多进程”的方式来完成的并发编程,实现bgsave
RDB的bgsave执行流程
- 父进程:redis服务器
- 虚拟空间:内存中的数据
- fork:进程和线程中,Java进行并发编程,主要是通过“多线程”的方式,fork是Linux系统提供的一个创建子进程的api(系统调用),其他系统创建子进程就不是fork(CreateProcess),fork创建子进程,简单粗暴,就是直接把当前的进程(父进程)复制一份(会复制pcb,虚拟空间,文件描述符表),作为子进程,一旦复制完成了,父子进程就是两个独立的进程,就各自执行各自的了,父进程打开了一个文件,fork了之后,子进程也是同样使用这个文件的,也就导致了子进程持久化写入的那个文件和父进程本来要写的文件是同一个了
- 本来redis server中有若干变量,保存了一些键值对数据,随着这样的fork的进行,子进程的这个内存里也会村内在和刚才父进程中一模一样的变量,因此复制出来的“子进程”的内存中的数据就是和“本体”是一样的,接下来安排子进程去执行“持久化”操作,也就相当于把父进程本体这里的内存数据给持久化了
- 在进行bgsave这个场景中,绝大部分的内存数据是不需要改变的(整体来说这个过程执行的还挺快的,这个短时间内,父进程不会有大批的内存数据的变化),因此,子进程的写时拷贝并不会触发很多次,也就保证了整体的“拷贝时间”是可控的,高效的
-
判定当前是否已经存在其他工作的子进程,比如现在有一个子进程正在执行bgsave,此时直接把当前的bgsave返回
-
如果当前没有其他的工作子进程,就通过fork这样的系统调用,创建出一个子进程来
-
子进程负责写文件,写成快照的过程,父进程继续接受客户端的请求,继续正常提供服务
-
子进程完成整体的持久化过程之后,就会通知父进程干完了,父进程就会更新一些统计信息,子进程就可以结束销毁了
Question:如果当前redis服务器中存储的数据特别多,内存消耗特别大,此时进行上述的复制操作,是否会有很大的性能开销?
Answer:此处的性能开销很小,fork在进行内存拷贝的时候,不是简单无脑的直接把所有数据都拷贝一份,而是“写时拷贝”的机制来完成的;如果子进程里的这个内存数据和父进程的内存数据完全一样了,此时就不会触发真正的拷贝动作(而是父进程和子进程用一份内存数据),但是,其实这俩进程的内存空间,应该是各自独立的,一旦某一方针对内存数据做了修改,就会触发真正的物理内存上的数据拷贝
接下来,父进程这里产生修改,此时子进程就会指向新的地址
RDB的rdb镜像文件
redis生成的rdb文件,是存放在redis的工作目录中的,也是在redis配置文件中,进行设置的
文件在/etc/redis/redis.conf中
- rdb机制生成的镜像文件,redis服务器默认就是开启了rdb的
- 二进制的文件,把内存中的数据,以压缩的形式,保存到这个二进制文件中;虽然需要消耗一定的cpu资源,但是能节省存储空间(http协议中的响应数据经常就会被压缩)
- 最多就是vim打开看看,不要乱改,一旦要是把数据的格式改坏了,就麻烦了
- 后续redis服务器重新启动,就会尝试加载rdb文件,如果发现格式错误,就可能会加载数据失败
- rdb文件,也有可能会出现一些意外问题,一旦通过一些操作(网络传输),引起这个文件被破坏,此时redis服务器就无法启动
- redis提供了rdb文件的检查工具,在使用命令时加一些选项来检查rdb文件
rdb持久化操作,是可以触发多次的:
当执行 生成rdb镜像 操作的时候,此时就会把要生成的快照数据,先保存到一个临时文件中,当这个快照生成完毕之后,在删除之前的rdb文件,把新生成的临时的rdb文件名字改成刚才的dump.rdb
RDB效果演示
当我们插入几条数据时,查看rdb.dump文件
rdb的触发时机
- 手动(save,bgsave)
- 自动(配置文件中,进行设置)
rdb文件中的数据,不是你这边插入了数据,就会立即更新的;刚才插入了几个键值对,没有运行手动触发的命令,也达不到自动触发的条件
自动触发的条件:在配置文件中-redis.conf
此处的数值均可修改,修改上述数据时,要有一个基本的原则
生成一个rdb快照,这个成本是一个比较高的成本,不能执行的太频繁
正因为rdb不能生成的太频繁,这就导致快照里的数据和当前实时的数据情况可能存在偏差
1.手动执行save & bgsave触发一次快照-因为数据较少,执行bgsave瞬间就完成了
重启redis服务器,可以看到,redis服务器在重新启动的时候,加载了rdb文件的内容,恢复了内存中之前的状态了
2.插入新key,不手动执行bgsave,重启redis服务器
正常重启redis服务器,数据不会丢失;强制关闭redis服务器,数据自动丢失
没有执行bgsave,但是key在重启之后仍然存在,备份文件中同样也存在
save 60 10000 (两次生成rdb之间的间隔,最少得是60s)
12.00.00生成了rdb(硬盘上的快照和内存中一致)
12.00.01开始,redis收到了大量的key的变化请求
12.01.00生成下一个快照文件
在这之间,redis服务器挂了,此时就会导致12.00.00之后的这些数据丢失;此时就可以使用AOF策略
redis生成快照操作,不仅仅是手动执行命令才触发,也可以自动触发
通过配置文件save执行M时间内,修改N次
通过shutdown命令(redis里的一个命令)关闭redis服务器,也会触发(service redis-server restart)
redis进行主从复制的时候,主节点也会自动生成rdb快照,然后把rdb快照文件内容传输给节点
如果异常关闭redis,可以看到redis服务器又瞬间启动另一个进程,此时新的key就不存在了
3. bgsave操作流程是创建子进程,子进程完成持久化操作,持久化会把数据写入到新的文件中,然后使用新的文件替换旧的文件(可以观察)
使用stat命令,查看文件的inode编号:
文件标识已经改变
Linux文件系统
文件系统的典型组织方式(ext4)主要把文件系统分成三个大部分
超级块(存放的是一些管理信息)
inode区(存放inode节点,每个文件都会分配一个inode数据结构,包含了文件的各种元数据)
block区,存放文件的数据内容
如果使用save,不会触发子进程和文件替换的,而是在当前进程中同一个文件中写入数据
4.通过配置完成自动生成rdb快照
执行flushall也会清空rdb文件
重启服务器-对于redis来说,修改配置文件后重启服务器才能生效
如果想立即生效,也可以通过命令的方式
如果在配置中设置为 save “” 代表着关闭自动生成快照
5.如果把rdb文件,故意改坏了,会咋样?
一定要使用kill进程的方式重启启动redis服务器(使用service会把修改的文件重新生成rdb快照)
看起来好像没影响?
这里具体redis会怎么样,取决于rdb文件坏了的地方在哪,如果中间坏了,不一定有什么影响
当把中间改坏了,发现启动不了redis了:查看redis日志
这个文件也是在配置文件中设置的
在最下面可以找到文件报错
在rdb恢复数据的过程中出现了问题了
rdb文件是二进制的,直接把坏了的rdb文件交给redis服务器去使用,得到的结果是不可预期的;可能redis服务器能启动,但是得到的数据可能正确也可能有问题,也可能redis服务器直接启动失败
如何解决?
redis也提供了rdb文件的检查工具-可以检查一下rdb文件格式是否符合要求
检查工具和redis服务器,在5.0版本是同一个可执行程序,可以在运行的时候加入不同的选项,从而使用不同的功能
运行的时候,加入rdb文件作为命令行参数,此时就是以检查工具的方式来运行,不会真的启动redis服务器
RDB文件的优点
RDB 是⼀个紧凑压缩的⼆进制⽂件,代表 Redis 在某个时间点上的数据快照。⾮常适⽤于备份,全量复制等场景。⽐如每 6 ⼩时执⾏ bgsave 备份,并把 RDB ⽂件复制到远程机器或者⽂件系统中 (如 hdfs)⽤于灾备。
-
Redis 加载 RDB 恢复数据远远快于 AOF 的⽅式。
-
RDB ⽅式数据没办法做到实时持久化 / 秒级持久化。因为 bgsave 每次运⾏都要执⾏ fork 创建⼦进 程,属于重量级操作,频繁执⾏成本过⾼。
-
RDB ⽂件使⽤特定⼆进制格式保存,Redis 版本演进过程中有多个 RDB 版本,兼容性可能有⻛ 险。
RDB使用二进制的方式来组织数据,直接把数据读取到内存中,按照字节的格式取出来,放到结构体/对象中即可
AOF是使用文本的方式来组织数据的,则需要进行一系列的字符串切分操作
AOF的基本使用(append only file)
类似于MySQL的binloog,就会把用户的每个操作,都记录到文件中
当redis重新启动的时候,就会读取这个AOF文件中的内容,用来恢复数据;当开启AOF的时候,RDB就不生效了,启动的时候不再读取rdb文件内容了;AOF默认一般是关闭状态
默认为no,改为yes即打开AOF
AOF的文件名,所在的位置
在启动AOF的时候,我们前面修改的dump.rdb文件无所谓了,因为此时按照AOF来启动
AOF文件-AOF是一个文本文件,每次进行的操作,都会被记录到文本文件中~
通过一些特殊符号作为分隔符,来对命令的细节作出区分(不必细追)
AOF是否会影响到redis性能
Question:Redis虽然是一个单线程的服务器,但是速度很快,为啥速度快?重要原因,只是操作内存,引入AOF之后,又要写内存,又要写硬盘,还能和之前一样快嘛?
Answer:实际上是没有影响的,并没有影响到redis处理请求的速度
- AOF机制并非是直接让工作线程写入硬盘,而是先写入一个内存中的缓冲区,积累一波以后,再统一写入硬盘(大大降低了写硬盘的次数,写硬盘的次数对性能影响大),就像嗑瓜子,嗑一个瓜子扔一次和嗑一百个瓜子扔一次的效率是不同的
- 硬盘上读写数据,顺序读写的速度还是比较快的(比内存慢),随机访问速度比较慢,AOF是每次把新的操作写入到原有文件的末尾属于顺序读写
AOF缓冲区刷新策略
Question:如果把数据写入到缓冲区里,本质还是在内存中,而如果这个时候进程挂了,缓冲区的数据是否就丢了?
Answer:是的!需要取舍与权衡!
redis给出了一些选项,让程序员,根据实际情况决定怎么取舍->缓存区的刷新策略
刷新频率越高,性能影响就越大,同时数据的可靠性就越高;刷新频率越低,性能响就越小,同时数据的可靠性就越低
在配置文件中给出一个选项:appendfsync
AOF的重写机制
AOF文件持续增长,体积越来越大,会影响到redis下次启动的启动时间,redis启动的时候要读取AOF文件的内容;上述AOF中的文件有一些内容是冗余的
例如有一个客户端对redis进行操作,进行操作lpush key 111,lpush key 121,lpush key 131,上述操作可以合并为lpush key 111 121 131;记录了中间的过程,实际上redis在重新启动的时候只关注最终结果
因此,redis就存在一个机制-重写机制(rewrite),能够针对AOF文件进行整理操作,这个整理操作能够提出其中的冗余操作,并且合并一些操作,达到给AOF文件瘦身这样的效果
AOF 重写过程可以⼿动触发和⾃动触发:
- ⼿动触发:调⽤ bgrewriteaof 命令。
- ⾃动触发:根据 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 参数确定⾃动触发时 机。
- auto-aof-rewrite-min-size:表⽰触发重写时 AOF 的最⼩⽂件⼤⼩,默认为 64MB。
- auto-aof-rewrite-percentage:代表当前 AOF 占⽤⼤⼩相⽐较上次重写时增加的⽐例。
AOF重写的流程
- 创建子进程-fork
- 父进程仍然负责接受请求,子进程负责针对AOF文件进行重写;重写的时候不关心AOF文件中原来都有啥,只是关心内存中最终的数据状态
- 子进程只需要把内存中当前的数据获取出来,以AOF的格式写入到一个新的AOF文件中(内存中的数据的状态,就已经相当于是把AOF文件结果整理后的摸样了)
- 此处子进程写数据的过程,非常类似于RDB生成一个镜像快照,只不过RDB这里是按照二进制的方式来生成的,AOF重写,则是按照AOF这里要求的文本格式来生成的(都是为了把当前内存中的所有数据状态记录到文件中)
- 子进程写新AOF文件的同时,父进程仍然在不停的接收客户端的新的请求,父进程还是会写把这些请求产生的AOF数据先写入到缓冲区,再刷新到原有的AOF文件里
- 在创建子进程的一瞬间,子进程就继承了当前父进程的内存状态;因此,子进程里的内存数据是父进程fork之间的状态,fork之后新来的请求对内存造成的修改,是子进程不知道的
- 此时,父进程这里又准备了一个aof_rewrite_buf缓冲区,专门放fork之后收到的数据,子进程这边把AOF数据写完之后,会通过信号通知一下父进程,父进程再把aof_rewrite_buf缓冲区中的内容也写入到新的新AOF文件里
Question:
- 如果,在执行bgrewriteaof的时候,当前redis已经正在进行aof重写了,会怎样呢?
- 如果,在执行bgrewriteaof的时候,发现当前redis在生成rdb文件的快照,会咋样呢?
- rdb对于fork之后的新数据,就直接置之不理了,aof对于fork之后的新数据,采取了aof_rewrite_buf缓冲区的方式来处理,为什么rdb不这样做?
- 父进程fork完毕之后,就已经让子进程写新的aof文件了,并且随着时间推移,子进程很快就写完了新的文件,要让新的aof文件代替旧的,父进程此时还在继续写这个即将消亡的旧的aof文件嘛?
Answer:
- 此时,不会再次执行AOF重写,直接返回了!
- 此时,aof重写操作就会等待,等待rdb快照生成完毕之后,在进行执行aof重写!
- rdb这样做,也就变成了aof,rdb本身的设计理念就是“定期备份”的,只要是定期备份就难以和最新的数据保持一致,aof的理念则是实时备份!
- 考虑极端情况:服务器挂了,子进程内存的数据会丢失,新的aof文件内容还不完整,所以如果父进程不坚持写旧的aof文件,重启就无法保证数据的完整性了
混合持久化
在备份文件中看到新添加的数据
下图是aof文件,看起来和rdb文件相似
AOF本来是按照文本的方式来写入文件的,但是文本的方式写文件,后续加载的成本是比较高的
redis就引入了“混合持久化”的方式,结合了rdb和aof的特点;按照aof的方式,每一个请求/操作,都记录入文件,在触发aof重写之后,就会把当前内存的状态按照rdb的二进制格式写入到新的aof文件中,后续在进行的操作,仍然是按照aof文本的方式追加到文件后面
这个选项为yes表示开启混合持久化
修改配置之后要重启服务器
Question:当redis上同时存在aof文件和rdb快照的时候,此时以谁为主?
Answer:以aof为主,rdb就直接被忽略了,因为AOF中包含的数据比RDB更全!
对于信号的解释
信号可以认为是Linux的神经系统:进程之间的相互作用(进程间通信的一种手段)
信号能表达的信息有限,并非像socket这样的方式可以传输任意的数据;因此,像上述父子进程场景中,子进程表达“我干完了”这种简单的信息传递,使用信号也是ok
信号可能接近于js里的“事件”:可以理解成Linux内核版本的事件机制
总结
Redis 提供了两种持久化⽅案:RDB 和 AOF
RDB 视为内存的快照,产⽣的内容更为紧凑,占⽤空间较⼩,恢复时速度更快。但产⽣ RDB 的开 销较⼤,不适合进⾏实时持久化,⼀般⽤于冷备和主从复制
AOF 视为对修改命令保存,在恢复时需要重放命令。并且有重写机制来定期压缩 AOF ⽂件
RDB 和 AOF 都使⽤ fork 创建⼦进程,利⽤ Linux ⼦进程拥有⽗进程内存快照的特点进⾏持久化, 尽可能不影响主进程继续处理后续命令