Redis持久化机制
使用缓存的时候,我们经常要做数据同步或者对部分机器重启后恢复数据。因此我们不可避免的要是要到缓存的持久化。
Redis不同于Menached的很重要一点也是,Redis支持持久化
所以本文,让我们一起来学习Redis的持久化方式
目前Redis支持的持久化有三种
- 快照(snapshotting,RDB)
- 只追加文件(append-only file, AOF)
- RDB 和 AOF 的混合持久化(Redis 4.0新增的)
RDB持久化
什么是RDB持久化
使用过服务器或者虚拟机的伙伴,对快照应该不陌生。你可以把快照当作一模一样的副本
而Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而获得一模一样数据的Redis,也可以将快照留到原地以便重启服务器的时候使用
RDB创建快照时会阻塞主线程吗?
Redis提供了两个命令来生成RDB快照文件:
- save:同步保存操作,会阻塞Redis主线程
- bgsave:fork出一个子进程,子进程执行,不会阻塞Redis主线程,默认选项。
执行快照时,数据能被修改吗?
在执行快照的时候,数据是能被修改的。这边我们就要提到一个关键技术:写时复制技术
在执行bgsave命令的时候,会先通过fork创建子进程,这个时候子进程和主进程共享同一片内存数据的,因为创建子进程的时候,会复制父进程的页表,但是页表指向的物理内存还是一个。
但是当发送内存数据修改的情况时,物理内存才会被复制一份
也就是说如果主进程没有修改数据的话,主线程和子线程用的是一个物理内存,但是如果主线程修改数据的话,对应的数据内存就会复制一份,然后主线程先对复制的内存进行数据修改,后续读取改数据的时候也是读取复制的数据内存。当然这个时候,子线程还是先根据原来的内存地址先将数据写入到快照中。
当然这有一个问题就是在快照期间修改的数据,并没有直接更新到快照中,只能等到下一次的快照中才能更新到快照中去。
AOF持久化
什么是AOF持久化
我们一起来思考一下下面的一种恢复方式,如果我们把Redis的每一条写操作命令都记录下来保存,当我们需要恢复数据或者拷贝数据的时候。我们将写操作记录命令记录的文本在执行一遍不就相当于恢复数据了吗?
这种保存写操作命令到日志的持久化方式,就是Reids里的AOF持久化功能,需要注意的是只会记录写操作命令,读操作命令是不会被记录的,读命令没有记录的必要。
在Redis中AOF持久化功能默认是不开启的,需要开启的话,需要修改配置文件redis.conf中的
appendonly yes //表示是否开启AOF持久化
appendfilename “appendonly.aof" //AOF持久化文件的名称
且Redis都是先执行写操作后才写将命令写入AOF的,这样子的操作有两个好处。第一个是避免额外的检擦开销还有一个就是不会阻塞当前写操作命令的执行。
AOF的操作流程大概是怎么样的
AOF持久化功能的实现可以简单分为5步:
- 命令追加(append):所有的写命令会追加到AOF缓冲区中
- 文件写入(write):将AOF缓冲区的数据写入到AOF文件中。这一步需要调用write函数(系统调用),write将数据写入了系统内核缓冲区之后直接就返回了(延迟写),所以说并没有同步到磁盘中。
- 文件同步(fsync):AOF缓冲区根据对应的持久化方式向硬盘做同步操作。这一步需要调用fsync函数(系统调用),fsync针对单个文件操作,对其进行强制的硬盘同步,fsync将阻塞直到写入磁盘完成后返回,保证了数据的持久化
- 文件重写(rewrite):随着AOF文件越来越大,就需要区定期对AOF文件重写达到压缩的目的
- 重启加载(load):当Reids重启时,可以加载AOF文件对数据进行恢复
需要注意的是write是写入到系统内核缓冲区之后直接返回(仅仅是写到缓冲区中),不会立即同步到硬盘中。这个操作虽然提高了效率,但是要注意是的带来了一定的数据丢失的风险。而fsync用于强制刷新系统内核缓冲区,确保写磁盘操作结束后才会返回。
AOF持久化的方式有什么?
前面我们了解了AOF的大致的流程,我们指导了rewrite并不会立马将数据写入到磁盘中,而是写入到了系统的缓冲区中,而是fsync才会强制将数据写入到系统磁盘中,完成数据的持久化。因此我们需要来了解一下在Redis的配置中存在的三种不同的AOF持久化方式(fsync策略)也就是写回策略。
- Always:我们从单词的表面意思上就能大概明白其的意思,总是。也就是意味着每次有写操作后,就将AOF日志的数据同步到硬盘中
- Everysec:从单词的意思上出发,每秒。也就是当写操作之后,每隔一秒就将缓冲区的数据写回到硬盘中
- No:也就是不写回。也就是Redis不控制写回硬盘的时机,转交给操作系统控制写回的时机。也就是每次写操作完后,先将写操作写到AOF的内核缓冲区,再由操作系统决定何时将缓冲的内容写回硬盘中。
从前面的介绍,我们很清楚的就能看到这3个写回策略的区别,也就是关于fsync 同步AOF文件的时机不同吧。但是有一个很值得关注的方面就是这三种写回策略都无法完美解决主进程阻塞和减少数据丢失的问题,因为这个两个问题的相对的,目前来看不可能两个都得到满足。
我们一起来看看每个写回策略的优缺点吧。
Always策略的话,可以很好的保证数据最大的程度不丢失,但是因为每有一个写操作就会导致刷盘的操作,所以会很大程度上去影响主进程的效率
No策略的话,就和我们刚刚介绍的Always策略相反了吧。因为其是交给操作系统,由操作系统来决定刷盘的时机。因此,其数据丢失的风险是最大的,一旦机器出现了问题,其丢失数据是大量的。但是相反其带来的性能是这几个策略中最好的,因为其不会阻塞主进程吧,因此其的性能效率是最高的。
我们最好再来看看Everysec这个策略,这个策略就有点像折中的策略。避免了像Always太频繁的刷盘导致的性能的下降,同时也避免了像No的完全交给系统来决定刷盘时机而导致容易丢失大量数据的数据丢失风险,当然如果上一秒的写操作命令日志没有写回到硬盘中的话发送了宕机,这一秒内的数据自然也会丢失了。
因此我们可以根据系统的业务场景进行选择:
- 高性能 选择No
- 高可靠 选择Always
- 如果允许少部分数据丢失且想性能高一点,选择Everysec
为什么AOF是在执行完命令后记录日志的
像我们平常使用的MySQL是先记录日志再执行命令,而Reids AOF 持久化机制是在执行完命令之后再记录日志的。
优点我们前面提到了一嘴,这边再简单说一下吧
- 避免额外的检擦开销,AOF记录日志不会对命令进行语法检查
- 在命令执行之后再记录,不会阻塞当前的命令执行
但是天上不会掉馅饼,这样子同样带来了一些风险
- 如果刚刚执行完命令,还没来得及写日志 这个时候宕机了 就会造成数据丢失
- 可能会阻塞后续其他的命令的执行
AOF重写
如果当写操作的命令太多了,那么就会导致AOF太大了。那么这个时候Redis在后台就会自动重写AOF产生一个新的AOF文件,这个AOF文件会比原来的AOF文件所保存的数据一致但体积会小的更多。
但是我们要知道的是,重写会进行大量的写入操作,为了避免影响Redis在期间正常处理命令。Reids将为AOF的重写放到新的子进程里执行。
AOF重写期间,Redis会维护一个AOF重写缓冲区,这个重写缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新的AOF文件保存的数据状态和现有的数据库状态一致。最后再由新的AOF文件来替换原本的AOF文件,这样子重写就完成了。
RDB和AOF合体
RDB的好处就是其恢复数据被AOF快很多,但是因为快照的频率不好控制。太快的话,会导致频繁写入磁盘和创建子进程带来不必要的性能开销。太慢的话,又会导致数据大量丢失的风险。我们又知道了AOF虽然数据丢失的少但是恢复的速度没有RDB这么快。
有没有将两者优点合并的方法呢,那就是将RDB和AOF合体使用,混合使用AOF日志和内存快照,也就混合持久化
混合持久化工作在AOF日志重写过程
当开启了混合持久化,在AOF重写日志时,fork出来的重写子进程会先将与主线程共享的内存数据以RDB方式写入到AOF文件中,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以AOF方式写入到AOF文件,写入完成后通知主线程将心的含RDB格式和AOF格式的AOF文件替换原来的AOF文件
也就是说,使用了混合持久化,AOF文件的前半部分是RDB格式的全量数据,后半部分是AOF格式的增量数据