一、为什么要持久化?
Redis对数据的操作都是基于内存的,当遇到了进程退出、服务器宕机等意外情况,如果没有持久化机制,那么Redis中的数据将会丢失无法恢复。有了持久化机制,Redis在下次重启时可以利用之前持久化的文件进行数据恢复。
Redis的持久化方式有RDB和AOF两种。
二、RDB持久化
RDB持久化也即是快照,把当前Redis中全部数据生成快照保存在硬盘上。快照是内存数据的二进制序列化形式,在存储上非常紧凑。RDB持久化可以手动触发,也可以自动触发。
1.手动触发
save和bgsave命令都可以手动触发RDB持久化。
(1)save命令
执行save命令会手动触发RDB持久化,但是save命令会阻塞Redis服务,直到RDB持久化完成。当Redis服务储存大量数据时,会造成较长时间的阻塞,不建议使用。
(2)bgsave命令
执行bgsave命令也会手动触发RDB持久化,和save命令不同是:Redis服务一般不会阻塞。Redis进程会执行fork操作创建子进程,RDB持久化由子进程负责,不会阻塞Redis服务进程。Redis服务的阻塞只发生在fork阶段,一般情况时间很短。
这意味着单线程同时在服务线上的请求还要进行文件 IO 操作,文件 IO 操作会严重拖 垮服务器请求的性能。还有个重要的问题是为了不阻塞线上的业务,就需要边持久化边响应 客户端请求。持久化的同时,内存数据结构还在改变,比如一个大型的 hash 字典正在持久 化,结果一个请求过来把它给删掉了,还没持久化完呢,这尼玛要怎么搞? 那该怎么办呢? Redis 使用操作系统的多进程 COW(Copy On Write) 机制 来实现快照持久化,这个机制很有意思,也很少人知道。多进程 COW 也是鉴定程序员知识广度的一个重要指标。
子进程做数据持久化,它不会修改现有的内存数据结构,它只是对数据结构进行遍历读
取,然后序列化写到磁盘中。但是父进程不一样,它必须持续服务客户端请求,然后对内存
数据结构进行不间断的修改。
这个时候就会使用操作系统的 COW 机制来进行数据段页面的分离。数据段是由很多操
作系统的页面组合而成,当父进程对其中一个页面的数据进行修改时,会将被共享的页面复
制一份分离出来,然后对这个复制的页面进行修改。这时子进程相应的页面是没有变化的,
还是进程产生时那一瞬间的数据。
随着父进程修改操作的持续进行,越来越多的共享页面被分离出来,内存就会持续增 长。但是也不会超过原有数据内存的 2 倍大小。另外一个 Redis 实例里冷数据占的比例往 往是比较高的,所以很少会出现所有的页面都会被分离,被分离的往往只有其中一部分页 面。每个页面的大小只有 4K,一个 Redis 实例里面一般都会有成千上万的页面。 子进程因为数据没有变化,它能看到的内存里的数据在进程产生的一瞬间就凝固了,再 也不会改变,这也是为什么 Redis 的持久化叫「快照」的原因。接下来子进程就可以非常安 心的遍历数据了进行序列化写磁盘了。
- 执行bgsave命令,Redis进程先判断当前是否存在正在执行的RDB或AOF子线程,如果存在就是直接结束。
- Redis进程执行fork操作创建子线程,在fork操作的过程中Redis进程会被阻塞。
- Redis进程fork完成后,bgsave命令就结束了,自此Redis进程不会被阻塞,可以响应其他命令。
- 子进程根据Redis进程的内存生成快照文件,并替换原有的RDB文件。
- 子进程通过信号量通知Redis进程已完成。
注意:
(1)在bgsave命令执行期间,客户端发送的save/bgsave命令会被服务器拒绝。
服务器禁止SAVE命令和BGSAVE命令同时执行是为了避免父进程(服务器进程)和子进程同时执行两个rdbSave调用,防止产生竞争条件。同样,同时执行两个BGSAVE命令也会产生竞争条件
(2)BGREWRITEAOF和BGSAVE两个命令不能同时执行
这两个命令在操作方面并没有什么冲突的地方,不能同时执行它们只是一个性能方面的考虑——并发出两个子进程,并且这两个子进程都同时执行大量的磁盘写入操作,这怎么想都不会是一个好主意.
自动触发
- 除了执行以上命令手动触发以外,Redis内部可以自动触发RDB持久化。自动触发的RDB持久化都是采用bgsave的方式,减少Redis进程的阻塞。那么,在什么场景下会自动触发呢?
- 在配置文件中设置了save的相关配置,如sava m n,它表示在m秒内数据被修改过n次时,自动触发bgsave操作。
- 当从节点做全量复制时,主节点会自动执行bgsave操作,并且把生成的RDB文件发送给从节点。
- 执行debug reload命令时,也会自动触发bgsave操作。
- 执行shutdown命令时,如果没有开启AOF持久化也会自动触发bgsave操作。
RDB文件的载入:
- 服务器启动时自动执行;
- 没有专门用于载入RDB文件的命令;
- 服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止;
- AOF持久化对RDB持久化的影响:如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态,那么就不会使用RDB文件了。只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。
RDB持久化的优点:
- RDB文件是一个紧凑的二进制压缩文件,是Redis在某个时间点的全部数据快照。所以使用RDB恢复数据的速度远远比AOF的快,非常适合备份、全量复制、灾难恢复等场景。
RDB持久化的缺点:
- 每次进行bgsave操作都要执行fork操作创建子经常,属于重量级操作,频繁执行成本过高,所以无法做到实时持久化,或者秒级持久化。
- 另外,由于Redis版本的不断迭代,存在不同格式的RDB版本,有可能出现低版本的RDB格式无法兼容高版本RDB文件的问题。
三、AOF持久化
AOF(Append Only File)持久化是把每次写命令追加写入日志中,当需要恢复数据时重新执行AOF文件中的命令就可以了。AOF解决了数据持久化的实时性,也是目前主流的Redis持久化方式。
AOF持久化流程
- 命令追加(append):所有写命令都会被追加到AOF缓存区(aof_buf)中。
- 文件同步(fsync):根据不同策略将AOF缓存区同步到AOF文件中。
- 文件重写(rewrite):定期对AOF文件进行重写,以达到压缩的目的。
- 数据加载(load):当需要恢复数据时,重新执行AOF文件中的命令。
AOF重写
Redis 提供了 bgrewriteaof 指令用于对 AOF 日志进行瘦身。其原理就是开辟一个子进程对内存进行遍历转换成一系列Redis 的操作指令,序列化到一个新的 AOF 日志文件中。序列化完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了,瘦身工作就完成了。=
fsync文件同步策略
- always:每次写入缓存区都要同步到AOF文件中,硬盘的操作比较慢,限制了Redis高并发,不建议配置。
- no:每次写入缓存区后不进行同步,同步到AOF文件的操作由操作系统负责,每次同步AOF文件的周期不可控,而且增大了每次同步的硬盘的数据量。
- eversec:每次写入缓存区后,由专门的线程每秒钟同步一次, 做到了兼顾性能和数据安全。是建议的同步策略,也是默认的策略。
四、Redis4.0 混合持久化
重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在Redis实例很大的情况下,启动需要花费很长的时间。Redis 4.0为了解决这个问题,带来了一个新的持久化选项——混合持久化。将rdb文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分AOF日志很小。
于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。