前言
Redis之所以足够快,一部分的原因是它的数据都是基于内存存储的。虽然Redis更多的是作为一个旁路缓存【先在redis中查询,查询不到数据的时候,在查询持久化数据库】来使用。如果说服务器一旦宕机,内存中的数据就会全部丢失,即便是后端服务可以通过查询持久化数据库进行恢复,但是对于数据库来说压力会变大,从而有可能打崩数据库。这在业务高峰期是非常危险的,好在Redis提供了持久化机制,避免因为实例崩溃导致数据全部丢失。当然也正是Redis的持久化机制,在业务中也是存在直接将Redis缓存作为持久化数据库的。Redis提供了两种持久化机制,RDB快照以及AOF日志。
RDB内存快照
所谓的RDB(内存快照)就是把Redis内存中某一时刻的状态以文件的形式写到磁盘上,即:实现类似照片记录效果的方式。但是对于Redis用户来说有三个问题必须要探究:1.对什么数据做快照;2.快照期间是否会阻塞主线程,会阻塞多久;3.使用快照恢复数据要多久。
1.RDB对什么数据做快照
为了提供数据的可靠性保证,RDB执行的是全量快照,即将内存中所有的数据都转储为文件到磁盘上。由于是将内存数据转储为文件,所以需要对内存中的数据进行扫描,实例内存越大耗费的时间自然更多,转储到磁盘上的IO开销自然越大。
2.做快照期间主线程是否会阻塞?阻塞多久?
内存快照既然是内存再某一时刻的状态,即它反映的是内存在某一个时刻的事实数据,所以这个事实数据时不允许被改变的。既然快照期间内存数据不能被修改是不是意味着主线程会被阻塞?实际上并没有,Redis采用save和bgsave两个命令执行内存快照。save命令是在主线程中执行,会导致阻塞,直至快照完成;而bgsave是调用了操作系统提供的fork函数创建了一个子进程,专门用于写入RDB文件,bgsave也是Redis默认的配置。
fork操作是操作系统提供的一个内核API,redis在执行RDB或AOF重写时,会fork一个子线程去完成,并且使用CopyOnWrite的机制节约内存。但是fork出一个子线程的时候需要拷贝一个线程所必要的数据结构以及页表(虚拟内存与物理内存的映射)这个操作主线程必然是阻塞的。相对来说线程的数据结构是相对固定的,但是页表是和实例的内存大小相关的,实例内存越大,拷贝的页表的耗时也自然更大,延时自然更长。
所以从快照期间Redis的主线程其实是会被阻塞的。如果是采用save命令执行,主线程的阻塞将会持续到快照结束;而如果采用的是bgsave命令完成,主线程的阻塞将会发生在fork调用的期间,阻塞的时长主要取决于实例所占用的内存大小。此外,有一个不得不注意的是如果对redis的主线程进行了核绑,由于fork出的子线程会继承父线程的核绑特性,所以父子线程会出现争用CPU的情况。此时,快照耗时将会大大延长,还不如直接使用save命令完成。因此在生产环境中,将redis的线程进行核绑的明令禁止的。
3.使用内快照恢复数据需要多久?
庆幸的是,RDB文件是一个特殊的二进制紧凑压缩文件,在使用它用来恢复数据速度会比较快。这里的快其实是相对于使用AOF文件恢复相同的数据量的情况下而言的,当然也只有在这个前提下比较才会有意义。
尽管RDB可以解决Redis的数据宕机恢复问题,但是对于多久执行一次快照是一个令人纠结的地方。如果间隔时间过长,那么一次宕机所丢失的数据就越多,但是间隔时间太小,虽然丢失的数据少了,但是对于CPU和磁盘的开销就更大了,尤其是在生产环境中基本上单个节点就是上百G的内存数据。基于此Redis提供了另外一种持久化方式AOF解决实时数据的持久化。
AOF日志
Redis提供了AOF(append only file)持久化方式解决实时持久化的问题。AOF以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。开启AOF功能需要设置配置:appendonly yes,默认不开启。 AOF文件名通过appendfilename配置设置,默认文件名是appendonly.aof。 保存路径同RDB持久化方式一致,通过dir配置指定。同样对于Redis用户来说有三个问题必须要探究:1.对什么数据做日志记录;2.实时日志持久化磁盘会不会阻塞主线程或是降低主线程的执行效率;3.AOF文件过大怎么办;4.使用AOF恢复数据需要多久。
1.对什么数据做日志记录
Redis的AOF机制只会针对写命令生效,并且是写后日志,即先执行命令,把数据写入内存,然后才记录日志。此外有一点需要注意的是,这里说的写命令在日志里边其实是已经根据RESP协议解析后的写命令,而并未原生的命令,而且对于有过期时间的key,会统一将过期时间修改为绝对时间。
2.AOF日志的同步机制
AOF的同步机制(为什么需要同步看最后)目前是有3个:
1.always:每次写入都要同步AOF文件,IO极为频繁,效率极低【看到某些博文说是由这个机制是由主线程完成,不确定,但自我猜测是必然的】
2.erverysec:每秒同步是建议的同步策略, 也是默认配置, 做到兼顾性能和数据安全性。 理论上只有在系统突然宕机的情况下丢失1秒的数据(实际为不超过2秒的数据),这个同步操作是由其他线程发起所以不会阻塞主线程。
3.no:将文件的刷盘同步交由操作系统完成,所以同步周期不可控,性能是最好的,但是数据的可靠性无法保证。
所以,AOF的实时日志持久化磁盘会不会阻塞主线程或是降低主线程的执行效率,需要看具体的配置,一般情况下都会采用每秒同步一次的机制,因为这个机制兼顾到了性能以及数据的可靠性。
3.AOF文件过大怎么办
如果Redis长时间采用AOF记录实时写日志,那么必然导致AOF文件的过大。如果是并发写命令足够高的业务,一天就足以达到几个G,甚至几十G都是可能的。如果AOF文件过大其实有比较多的问题:1.文件大小本身受到文件系统的限制;2.存储成本以及备份所需要的时间更多;3.最重要的是使用AOF文件恢复时会更慢。所以Redis提供了AOF的重写机制。所谓的重写机制即是将多个命令合并为一个命令,因为在恢复数据的时候,我们关心的是最终的状态,而非过程。
那么AOF的重写日志什么时候会触发呢?Redis提供了bgrewriteaof命令来进行手动触发重写;并且提供了auto-aof-rewrite-min-size和auto-aof-rewrite-percentage配置自动触发重写,重写也是使用bgrewriteaof来完成的。
从图中可以看到bgrewriteaof的操作本身也是通过fork子线程来完成的,对主线程是会产生阻塞的。当fork操作完成之后,主线程仍旧可以继续执行读写命令,如果期间又有写命令执行的时候,这部分的增量日志如何处理呢?其实,这部分增量日志首先会被写入到AOF的重写缓冲池里,当AOF文件重写完成后会被同步到该文件中。
4.使用AOF文件恢复数据需要多久
AOF文件恢复数据时时通过重放日志的方式来完成的,所以文件越大,恢复的时间就越长,对比内存快照而言,是非常慢的。
AOF+RDB的结合
从上述中可以看出RDB的优点是全量复制,数据恢复快,但是耗费的CPU以及IO成本较高,宕机后丢失的数据也较多;AOF的优点是实时记录,宕机丢失数据定格在2秒内,但是其恢复速度较慢,适用于做增量日志。因此Redis提供了aof-use-rdb-preamble yes配置将RDB以及AOF同时开启作为持久化。
在重启Redis时,先加载RDB快照,然后在重放AOF日志,完成数据的恢复,大幅度提升Redis的重启效率。
备注:为什么用户程序将向文件写完之后还需要操作系统进行同步?
为了提高文件的写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面。如果同步文件之前, 如果此时系统故障宕机, 缓冲区内数据将丢失。为此,系统提供了fsync和fdatasync两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。
参考资料:
极客时间专栏课-Redis核心技术与实战
<<Redis开发与运维>>
<<Redis深度历险:核心原理与应用实践>>
原创文章,作者:,如若转载,请注明出处:https://blog.ytso.com/tech/database/275574.html