详细介绍了Redis的持久化机制,包括RDB与AOF持久化,以及混合持久化。
文章目录
1 数据持久化
为了重启机器、机器故障、系统故障之后恢复数据,将内存中的数据写入到硬盘里面,这就是持久化,Redis恰好支持数据的持久化,这也是相比于Memcached来说一个非常大的优点。
数据从内存持久化到磁盘,通常会需要经历两步:
- 由应用程序调用POSIX file API 中的write()函数将数据从用户进程缓冲区(程序内存)写入磁盘,这一步write()函数实际上仅仅写入到了内核缓冲区(或者说磁盘映射内存os cache,或者说文件系统的page cache),并没有真正实际写入磁盘。这一块区域仍然属于文件系统向内核申请的一块的内存区域,并没有真正的把数据持久化到磁盘,所以速度是比较快的。
- 当数据在内核缓存区中累积了足够多的数据之后(主要是为了提升性能),操作系统会自动的将这些缓存数据真正的持久化到物理磁盘中,这一步完成之后,数据才算真正的完成持久化。
如果第一步完成了的时候,出现了软件层面的问题,比如进程终止或程序崩溃,我们仍然可以说数据持久化成功了,因为只要操作系统没有问题,它最终会自动的将内核缓存区中的数据持久化到磁盘,所以数据是不会丢失的。
如果第一步完成了的时候,出现了操作系统层面的问题,比如直接停电了、操作系统出故障了等等灾难性事件,那么由于此时的数据仍然在内核缓存区中,将会导致数据的丢失。
默认情况下,Linux 系统将在 30 秒后实际提交写入。可以想象,如果在这三十秒之间主机停电了,那么将会导致应用丢失大量的数据。为此,应用程序可以调用POSIX file API 提供的另一个fsync()函数,该函数会将之强制内核将缓冲区的数据真正立即的写入磁盘,该函数每次调用都会同步阻塞直到设备报告IO完成,并且我们知道磁盘的IP是一个非常消耗性能的操作,所以一般在生产环境的服务器中,Redis 通常是每隔 1s 左右执行一次 fsync()操作。
上面说到了数据从用户内存到磁盘的一个持久化的底层流程,该流程几乎都是通用的(除非使用Zero-Copy等技术)。在应用程序的层面,Redis支待两种待久化策略(也就是什么时候执行持久操作),一种是RDB(Redis DataBase),另一种是AOF(append-only file),可以单独使用其中一种持久化方式或将二者结合使用,或者二者都不使用。
当持久化失败时,Redis将会停止任何的写入命令的执行,并返回错误。
2 RDB(Redis DataBase)快照
RDB是指在指定的时间间隔内针对key的写操作达到一定数量时,就将该时间点的内存中的数据集快照写入磁盘的一个新的.rdb文件中,即一种全量备份的方式。快照(snapshotting),顾名思义可以理解为拍照一样,把某个时刻整个内存数据映射到硬盘中。RDB适合做冷备。
RDB机制是Redis的默认持久化配置,在Redis.conf 配置文件中的默认配置如下:
save 900 1
save 300 10
save 60 10000
该机制表示,达到如下条件时Redis将执行持久化操作:
- 900 秒(15 分钟)后,至少更改了1个key;
- 300 秒(5 分钟)后,至少更改了10个key;
- 60 秒(1 分钟)后,至少更改了10000个key;
2.1 RDB的原理
Redis的工作线程是单线程Reactor模型,如果Redis使用主线程来执行RDB持久化的操作,那么将会极大的影响Redis的性能,因此Redis实际上是使用另一个子进程来做的持久化操作,而不是主线程。
Redis 在持久化RDB时会调用 glibc 的函数 fork 产生一个子进程,即基于当前进程复制了一个进程,子进程刚刚产生时,主进程和子进程会共享内存里面的代码块和数据段,这是 Linux 操作系统的机制,为了节约内存资源,所以尽可能让它们共享起来。
之后再由子进程完成这些RDB持久化的工作,处理客户端请求仍然是主进程处理,这样就可以极大的提供了Redis的性能。子进程做数据持久化时,它不会修改现有的内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘中。主进程则可以持续服务客户端请求,也可以对内存数据结构进行修改。
那么当子进程对某个数据进行备份时主进行街道修改该数据的命令之后怎么处理呢? Redis使用操作系统的COW(Copy On Write) 机制,当主进程中需要对数据段中的某一页的数据进行修改时,该数据页先会被完整的复制一份,然后主进程在复制的页面上更改,而子进程对应的原始数据页也是没有改变的,这样就保证了子进程看到那一时刻的数据是始终不变的,而在持久化过程中主进程又可以正常改动数据。这也是Redis的RDB持久化方式被称为快照(snapshotting)的原因,子进程看到的数据是不会变化的,定格在fork的那一时刻。
客户端可以使用BGSAVE
命令要求Redis服务器立刻执行RDB持久化操作。
2.1 RDB的优缺点
- RDB的优点
- RDB在每一次持久化的时候,都会生成一个新的.RDB文件,内部包含了该时刻Redis里面的全部数据,这种方式适合做冷备,用于定期全量的保存数据,并且可以方便的将该文件拷贝转移到其他地方,轻松实现多地灾备。
- RDB对Redis的性能影响非常小,因为在同步数据的时候他只是fork了一个子进程去做持久化的,对主线程无特殊影响。
- 另外如果数据集很大,RDB文件在启动数据恢复时的效率比AOF会更高,直接将整个数据集映射到内存中即可,无需再执行命令。
- RDB的缺点
- 由于RDB采用在某个时刻生成快照的方式持久化,那么如果Redis在两个RDB持久化操作之间挂了,这就意味着最后一次持久化之后的这段时间的数据直接丢失,而AOF则最多丢失一秒的数据。
- 由于RDB是通过fork子进程来协助完成数据持久化工作的,而在fork阶段会发生阻塞。因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。这对于关键业务比如秒杀来说是不能容忍的。
2 AOF(append-only file)追加
2.1 AOF的原理
AOF机制则是以一定的规则将服务器所处理的每一个增,删,改操作以追加(append-only)的形式记录到.aof磁盘文件中,就像记录操作日志一样。Redis是先执行指令,然后再写AOF日志的。
在Redis服务器启动之初将会读取该文件并来重新构建数据库,一条条的执行操作命令(重放日志),以保证启动后数据库中的数据是完整的,明显恢复数据的速度相比于rdb文件更慢一些,但是它也有自己的优点。
默认情况下,AOF机制是关闭的,可以通过将redis.conf的appendonly 参数设置为yes开启。两种机制全部开启的时候,Redis在重启时会默认使用AOF去重新构建数据,因为AOF的数据比RDB更完整。AOF适合做热备。
同样,redis.conf配置文件中存在三种不同的 AOF 同步(fsync刷盘)方式:
appendfsync always
:每次有数据修改发生时都会同步写入AOF文件,会严重降低Redis的速度。appendfsync everysec
:每秒钟同步一次,显示地将多个写命令同步到硬盘,这是Redis默认配置。appendfsync no
:从不主动同步,而是让操作系统决定何时进行同步。
2.2 AOF重写
由于AOF文件是追加的形式,因此AOF文件将会越来越大,将会占用越来越多的磁盘空间,对于启动重放AOF日志的操作也会更加耗时,必须想办法给AOF文件瘦身!
假如对于key a进行了以下一系列操作:
127.0.0.1:6379> SADD a aa bb cc
(integer) 3
127.0.0.1:6379> SADD a bb dd
(integer) 1
127.0.0.1:6379> SREM a cc
(integer) 1
127.0.0.1:6379> SMEMBERS a
1) "aa"
2) "bb"
3) "dd"
这一系列对于key a的操作,需要在AOF文件中追加保存3条命令,但其实此时key a的状态我们可以用一条命令来保存,如:SADD a aa bb dd。
以上,就是AOF文件瘦身的思路,三条命令的操作可以使用一条命令来保存,这被称为AOF重写。但实际上,AOF重写与此前的任务操作都无关,仅仅是保存当前该时刻数据库的键值对的状态,它根据当前内存中值所对应的类型,执行对应的命令操作,可以将AOF中的多条操作记录合并为同等语义的一条操作记录。
因为在进行AOF命令重写时会进行大量的写入操作,这时如果在服务器主进程中来进行AOF重写操作,会阻塞服务器主进程,导致其无法处理客户端的请求,为了避免这种情况的发生,AOF重写同样是在fork的子进程中来完成的,类似于RDB。
子进程会使用COW机制拷贝服务器进程的数据副本,AOF重写会对内存进行遍历转换成一系列Redis的操作指令,序列化到一个新的 AOF 日志文件中。同样,使用子进程带来的一个问题是当子进程进行AOF重写时服务器进程在这段期间会接收客户端的新的命令请求,这导致重写后的AOF文件与服务器的状态不一致的情况。
为了解决这个情况,Redis引入了“重写缓冲区”的概念,这个重写缓冲区在Redis创建AOF子进程重写后使用,在AOF重写期间,新到达的命令会在主进程中执行三个操作:
- 执行客户端的命令请求;
- 客户端的命令追加到现有AOF文件;
- 客户端的命令追加到重写缓冲区中;
当子进程完成了AOF文件的重写时,会像父进程发送一个重写完成的信号,父进程在接收到信号后,会将重写缓冲区的新增的指令内容同步到新的AOF文件中,最后重命名新的AOF文件,覆盖原有的AOF文件,实现AOF文件的“瘦身”(这一步是在父进程中操作的,将会短暂的阻塞其他网络请求的执行)。
总结起来就是:
- AOF重写,可以减少AOF文件的磁盘占用量,加快数据重启恢复的速度。
- AOF重写时,实际上是基于当前内存中数据状态来确定重写后的命令的,对于原来的AOF文件无关。
客户端执行BGREWRITEAOF指令即可命令Redis服务器立即执行AOF 日志重写。服务器也会在满足以下三个条件的情况下自动重写(自动调用BGREWRITEAOF)。
- 当前没有RDB和AOF在进行。
- Redis 会记住最近一次重写后的 AOF 文件的大小(如果重启后没有发生过重写,则使用启动时 AOF 的大小),Redis将此基本大小与当前AOF文件的大小进行比较,如果当前大小大于指定的百分比,则触发重写。通过redis.conf中配置的
auto-aof-rewrite-percentage
属性的大小,可以指定百分比,默认100,指定为0表示禁用AOF重写。 - 除了满足百分比之外,还有一个条件,那就是重写的 AOF 文件必须要大于等于指定最小大小,这对于避免重写 AOF 文件(即使达到百分比增加但仍然很小)很有用。当前AOF文件重写的最小大于在redis.conf中通过
auto-aof-rewrite-min-size
属性配置,默认64mb;
2.3 AOF的优缺点
- AOF的优点
- 如果可开启AOF机制并且使用appendfsync everysec同步刷盘机制,那么最多会丢失1秒钟的数据,相比于RBD机制数据安全性更高。
- AOF机制对日志文件的写入操作采用的是append-only模式,即追加写入,对于此前写入的命令数据没有任何影响,如果某次操作误删了数据,那么在AOF重写之前只需要立即找到AOF文件并且将“删除命令”删除,然后重启即可恢复原数据。
- 追加写入的方式为顺序写入无需过多的磁盘寻址。
- AOF的缺点
- 对于相同数量的数据集而言,AOF文件通常要大于RDB文件,启动Redis重新加载aof持久化文件的时间也会更久。
- 使用AOF之后,Redis能保证更好的数据安全性,但qps往往低于RDB。一般而言,每秒同步的策略的效率是比较高的。
3 混合持久化策略
如果采用RDB持久化,那么将可能丢失大量数据,如果采用AOF持久化,那么在启动时会慢很多。
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认开启,可以在redis.conf中通过通过配置aof-use-rdb-preamble
开启)。
混合持久化依赖aof文件,因此必须开启AOF持久化。主要就是在进行AOF重写时候,可以避免aof文件过大的问题。
开启混合持久化之后,RDB文件和AOF日志文件存在一起,aof文件内容格式是[RDB file][AOF tail] 明显特征是aof文件的开头为:REDIS。在进行重写的时候,内存中的原始数据被持久化为RBD格式的数据,而重写开始到重写结束(RDB持久化)期间的数据则记录为AOF格式的增量日志。
开启混合持久化之后,aof中的AOF日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量AOF日志,通常这部分AOF日志很小。在 Redis 重启的时候,可以先快速加载 RDB部分的内容,然后再重放增量 AOF 日志就可以完全替代之前的AOF全量文件重放,数据安全性和重启效率都得到大幅得到提升。
相关文章:
- https://redis.io/topics/data-types
- https://redis.io/topics/data-types-intro
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!