redis是一个内存数据库,是把数据存储在内存中的,而我们知道内存中的数据是不持久的,一旦服务器重启或者进程重启,内存的数据就丢失了.为了让数据达到持久化的效果,就必须把数据写到硬盘上.
redis相对于mysql这样的关系型数据库最明显的优势就是快.所以为了保证速度快,数据还得在内存中,但是为了持久,数据还要想办法存储在硬盘上.
Redis为了应对这样的情况,决定把数据在内存中存储一份,同时在硬盘上也存储一份.这样的两份数据,理论上是完全相同的,实际上可能存在差异,取决于我们具体怎么进行持久化.
当要插入一个新的数据的时候,就需要把这个数据,同时写入内存和硬盘.但说是同时写,实际上怎么写入硬盘是有不同的策略的,应用这些策略,就可以保证redis整体的效率还是足够高的.
当查询某个数据的时候,直接从内存读取.硬盘中的数据只是在redis重启的时候,用来回复内存中的数据的.
代价就是消耗了更多的空间,同一份数据存储了两份,但是硬盘空间毕竟是比较廉价的,这样的开销并不会带来很多的成本.
redis实现持久化的策略
redis实现持久化的整体策略有两种,RDB和AOF.
RDB:Redis DataBase.AOF:Append Only File.
RDB是定期备份,AOF是实时备份.
RDB持久化
RDB持久化是定期的把当前进程数据生成快照保存到硬盘的过程.
后续redis一旦重启了,就可以根据硬盘的中快照把内存中的数据给回复回来.
RDB定期备份的两种方式
1.手动触发
程序员通过redis客户端,执行特定的命令,来触发快照的生成.
save命令,执行save命令的时候,redis服务器会在单线程模型下全力以赴的进行快照生成的操作,此时就会阻塞redis其他客户端的命令,一般不建议使用save.
bgsave,bg是background的缩写,此命令不会影响redis服务器处理其他客户端的请求和命令.此命令是redis通过多进程的方式来完成并发编程从而实现bgsave.
2.自动触发
我们可以在redis配置文件中,设置一下,让redis每隔多长时间以及每产生多少次修改就触发备份操作.
redis生成rdb文件,是存放在redis的工作目录中的,redis的工作目录也是在redis配置文件中进行设置的.
我们不仅可以修改redis的工作目录,也可以修改生成的rdb文件的名字.
dump.rdb是RDB机制生成的镜像文件,redis服务器默认是开启了rdb的.此rdb文件时一个二进制的文件,把内存中的数据,以压缩的形式,保存到这个二进制文件中.(压缩会消耗一定的cpu资源,但是能节省存储空间).
这个二进制文件,我们不能随意修改.redis服务器重新启动,会尝试加载这个rdb文件,如果发现格式错误,就可能会加载数据失败.
需要注意的是,rdb文件,即使我们不去主动修改它,但是也可能会出现一些意外情况,一旦通过一些操作(比如网络传输)引起这个文件被破坏,此时redis服务器也是无法正常启动的.
rdb的持久化操作可以执行多次,当执行生成rdb镜像文件操作的时候,此时就会把要生成的快照数据,先保存到一个临时文件中,当这个快照生成完毕之后,在删除之前的rdb文件,把新生成的rdb临时文件名字改为刚才的dump.rdb,所以自始至终,rdb文件是始终只有一个的.
RDB自动触发的条件
我们可以在redis的配置文件中查看达到自动触发rdb的条件.
15分钟之后如果至少修改了一次或者5分钟之后至少修改了10次或者1分钟之后至少修改了10000次就会自动触发rdb机制.
注意:时间要满足的同时修改次数也要满足.
此处的数值都可以自行修改.但是修改上述的数据的时候,要有一个基本的原则:生成一个rdb快照,是一个比较高的成本,不能让这个操作执行的太过频繁.
也正是因为rdb生成的不能太过频繁,这就导致,快照里的数据,和当前实际的数据情况可能是存在偏差的.
手动执行bgsave触发一次生成快照
由于这里的数据比较少,执行bgsave瞬间就完成了,立即查看应该是有结果的.如果这里的数据比较多,执行bgsave就可能需要消耗一定的时间,立即查看不一定就是生成完毕了.
在rdb镜像文件里可以隐约的看到我们的插入的key.
我们重启redis服务器,再次进入redis客户端查看数据.
通过上述操作,就可以看到,redis服务器在重启的时候,加载了rdb文件的内容,恢复了之前内存中的数据状态.
插入新的key,不手动执行bgsave
我们插入一个新的key之后,重新启动redis服务器,再次查看内容.
可以看到我们新插入的key4依然存在,但是我们并没有手动执行bgsave,同时也没有达到自动触发的条件,这是什么原因呢?
这是因为如果是通过正常流程重新启动redis服务器,此时redis服务器会在退出的时候自动触发生成rdb的操作.但是如果是异常重启(kill -9或者服务器掉电),此时redis服务器来不及生成rdb,内存中尚未保存的数据就会随着redis启动而真的丢失了!!!
所以redis自动触发rdb会在多个场景中存在:
1.在配置文件中配置save 时间 次数,表示在多长时间之后,执行次数达到多少,才触发.
2.通过shutdown命令(不带参数),关闭redis服务器,此处的关闭属于是正常关闭,也会触发生成rdb快照的操作.我们上述的service redis-server restart也属于是正常关闭.
3.redis进行主从复制的时候,主节点也会自动生成rdb快照,然后把rdb快照文件内容传输给从节点.
如果正常情况的关闭,我们是不必担心内存数据丢失的情况,在实际开发中我们更担心异常情况的出现导致redis服务器异常关闭.比如使用kill命令(kill -9 redis进程id)的方式来直接杀死redis进程,此时就会导致新插入的数据的丢失.
注意在ubuntu系统下,由于我们是通过service的方式来启动redis,所以会存在一个守护进程来时刻检测redis的情况,当redis服务器挂掉之后,会迅速在拉起一个redis,所以虽然kill掉了redis进程,但是查看进程信息redis还存在,但是此时的进程id已经不一致了.
bgsave操作流程是创建子进程,由子进程完成持久化操作.
持久化会把数据写入到一个新的临时文件中,最后使用新的文件来代替旧的文件.
如果直接使用save命令,此时是不会创建子进程和进行文件替换的.save命令是直接在当前进程中,往同一个文件中写入数据,不会创建新的文件.
inode相当于是文件的标识,inode不同,说明文件已经不是同一个文件了.
通过配置自动生成rdb快照
我们可以在配置文件中修改save来设置自动生成快照的条件.对于redis来说,修改配置文件之后,一定要重启服务器,才能生效,如果想要立即生效,也可以通过命令的方式修改.
save " "是用来关闭自动生成快照的.
当我们把rdb的文件改坏了,会发生什么
手动的把rdb的文件改坏,然后一定是通过kill进程方式,重新启动redis服务器.
如果是通过service redis-server restart重启,就会在服务器退出的时候,重新生成rdb快照,就把刚才改坏掉的文件替换掉了,所以要使用kill的方式.
由于rdb文件时二进制的,直接把改坏掉的rdb文件交给redis服务器去使用,得到的结果是不可预期的.
如果改的地方正好是文件末尾,对前面的内容没有影响,可能redis再次启动还是可以恢复正常的数据的.
对于rdb文件损坏,可能redis服务器启动得到的数据正确性可能有问题,也可能redis服务器直接就启动失败了.
redis也提供了rdb文件的检查工具,可以先通过检查工具,检查一下rdb的文件格式是否是符合要求的.
AOF
当开启aof的时候,rdb就不生效了,reids服务器启动的时候就不再读取rdb文件的内容了.
会把用户的每个操作都记录到文件中.当redis重新启动的时候,就会读取这个aof文件中的内容,用来恢复数据.
aof默认一般是关闭状态,修改配置文件,来开启aof功能.
修改为yes开启.
aof文件所在的位置和rdb文件在同一目录下.(/var/lib/redis)
aof是一个文本文件,每次进行的操作都会记录到文本文件中,通过一些特殊符号作为分隔符,对命令的细节做出区分.
引入aof之后,既要写内存又要写硬盘,redis是不是就变慢了?
redis是一个单线程的服务器,速度很快(快就快在直接操作内存).引入aof之后,实际上对于redis来说,是没有影响的.并没有影响redis处理请求的速度.原因有两点:
1.aof机制并非是直接让工作线程将数据写入硬盘,而是先写入一个内存中的缓冲区,当缓冲区的数据积累到一定量之后,在统一写入内存.引入缓冲区之后,就大大降低了写硬盘的次数.写硬盘的时候,写入硬盘数据的多少,对于性能的影响不是很大,关键是写硬盘的次数多了,影响就比较大了.
2.硬盘上读写数据,顺序读写的速度是比较快的,随机访问速度是比较慢的.aof是每次把新的操作写入到原有的文件的末尾,属于是顺序写入.
如果把数据写入到缓冲区里,本质还是在内存中,如果这个时候,进程突然崩溃或者主机掉电,缓冲区中的数据还没来得及写入到硬盘中,那么这一部分数据就丢失了.
所以,redis给出了一些选项,来取舍刷新频率和性能.
刷新频率越高,那么对性能的影响就越大,但是数据的可靠性就越高.
刷新频率越低,性能影响的越小,但是数据的可靠性就越低.
aof重写机制
当aof文件持续增长,体积就会越来越大,会影响到redis下次启动时的启动时间.
为了解决这个问题,就出现了重写机制.
重写机制是因为在zof文件中,有一些内容是冗余的,比如对一个key修改了多次,但是我们在下次启动的时候,只是关注这个key的最终结果的,至于它是修改了几次我们是不关注的,比如删除一个key,或者set了多个key我们可以用mset命令.
重写其实就是对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文件结果整理后的模样了).
子进程写新的aof文件的同时,父进程仍然在不停的接收客户端的请求,父进程还是会把这些请求产生的aof数据先写入到缓冲区,在刷新到原有的aof文件中.
在创建子进程的瞬间,子进程就继承了当前父进程的内存状态.因此,子进程里的内存数据是父进程fork之前的状态,fork之后,新来的请求对内存造成的影响,是子进程感知不到的.
所以,此时父进程又准备了一个aof_rewrite_buf缓冲区,专门用来存放fork之后收到的数据.当子进程把aof数据写完之后,会通过信号通知父进程,父进程在把aof_rewrite_buf中的内容也写入到新的aof文件里.
此时就可以用新的aof文件代替旧的文件了.
如果在执行bgrewriteaof的时候,当前reids已经在进行aof重写了,此时就不会再次执行aof重写了,会直接返回.
如果,在执行bgrewriteaof的时候,当前redis正在生成rdb文件的快照,那么aof操作就会等待,等到rdb快照生成完毕之后,在进行执行aof操作.
rdb对于fork之后的数据,就直接置之不理了.aof则对于fork之后的数据,采取了aof_rewrite_buf缓冲区的方式来处理.这也很符合它们的设计理念.rdb只是用来定期备份(定期备份就难以和最新的数据保持一致),aof则是实时备份.
父进程fork完毕之后,就已经让子进程写新的aof文件了,并且一段时间过后,子进程很快的完成了工作,新的文件代替旧的文件.那么,父进程还有必要继续写这个即将被替换的旧的文件吗?
是有必要的.考虑到极端情况,假设在重写过程中,重写到一半,服务器崩溃了,子进程内存的数据就丢失了,而新的aof文件内容还不完整,所以如果父进程不坚持写旧的aof文件,在这种情况下,reids重启就无法保证数据的完整性了.
混合持久化
aof本来是按照文本的方式来写入文件的,但是文本的方式写文件,后续加载的成本是比较高的.
此时redis就引入了混合持久化的方式,结合rdb和aof的特点.
按照aof的方式,每一个请求或者操作都会记录到文件中.
在触发aof重写之后,就会把当前的内存状态按照rdb的二进制格式写入到新的aof文件,后续再进行的操作,仍然是按照aof文本的方式追加到aof文件后面.
yes表示开启混合持久化.