1. 前言

本文将先说明持久化、主存复制(及读写分离)、哨兵、以及集群几种技术分别解决了Redis高可用的什么问题;

然后详细介绍Redis的持久化技术,主要是RDB和AOF两种持久化方案;在介绍RDB和AOF方案时,不仅介绍其作用及操作方法,同时介绍持久化实现的一些原理细节及需要注意的问题。

最后,介绍在实际使用中,持久化方案的选择,以及经常遇到的问题等。

1.1 redis 如何支持服务高可用

在介绍Redis高可用之前,先说明在Redis的语境中高可用的含义。

我们知道,在Web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999% 等等)。但是在Redis语境中,高可用的含义似乎要宽泛一些,除了保证提供正常服务(主存分离、快速容灾技术)还需要考虑数据容量的扩展,数据安全不会丢失等。

在Redis中,实现高可用技术主要包括持久化、主存复制、哨兵和集群,下面分别说明他们的作用以及解决了什么问题。

  1. 持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
  2. 主存复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复(读写分离)。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制
  3. 哨兵:在复制的基础上,哨兵实现了自动化的故障转移。缺陷:写操作无法负载均衡;存储能力受单机限制
  4. 集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受单机限制的问题,实现了较为完善的高可用方案,不支持故障恢复以及自动转移

2. 夯实基础

2.1 什么是持久化

Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据永久丢失,需要定期将Redis中的数据以某种形式(数据/命令)从内存保存到磁盘;当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置;

2.2 持久化分类

1)RDB(Redisu Database)

RDB 持久化: 是指在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是 snapshot内存快照 ,它恢复时再将磁盘快照文件(dump.rdb)直接读回到内存。

实现类似照片记录效果的方式,就是把某一时刻的数据和状态以文件的形式写到磁盘上,也就是快照。这样一来即使故障宕机,快照文件也不会丢失,数据的可靠性也就得到了保证。

这个快照文件就称为RDB文件(dump.rdb),其中,RDB就是Redis DataBase的缩写。

2)AOF(Append Only file)

AOF持久化: 是指以日志的形式来记录每个写操作,将Redis所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据。

换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次已完成数据的恢复工具。

默认情况下,redis是没有开启AOF(append of file)的。开启AOF功能需要设置配置:

appendonly yes

AOF 保存的是 appendonly.aof 文件

3)RDB-AOF 混合持久化

疑问:rdb vs aof 可否共存?如果共存以哪种方式为准?

下面是配置文件的说明

数据恢复顺序与加载流程:在同时开启 rdb和aof 持久化时,重启时只会加载aof文件,不会加载rdb文件。

4)纯缓存模式

同时关闭 RDB+AOF

// 禁用rdbsave ""// 禁用aofappendonly no 

注意在禁用rdb和aof后仍然可以使用持久化命令生成对应的持久化文件。

  1. 禁用rdb的模式下,仍然可以使用命令 save bgsave 生成rdb文件。
  2. 禁用aof的模式下,仍然可以使用 bgrewriteaof 生成aof文件。

2.3 RDB 持久化

RDB保存到磁盘的文件叫dump.rdb

1)持久化流程

RDB 持久化是指在指定的时间间隔内将内存中的数据快照写入磁盘,实际操作过程是 fork 一个子进程,先将数据写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

第一步: 配置文件说明

redis 6.0.16以下配置


redis 6.2 以及redis 7

第二步: 2) 操作说明

操作主要分为手动触发自动触发两种方式。

方式一:自动触发

Redus 7 版本 按照 redis.conf 里配置的 save

比如 5秒修改2次

第三步:如何恢复

将备份文件(dump.rdb) 移动到redis安装目录并启动服务器即可。

验证一:备份成功后故意使用flushdb 清空redis,看看是否可以恢复数据。

验证二:物理恢复,服务和备份分机隔离

备注:不可以把备份文件dump.rdb和生产redis服务器放在同一台机器,必须分开各自存储,以防生产机物理损坏后备份文件也挂了。

方式二:手动触发

redis 提供了两个命令来支持生成rdb文件,分别是 save bgsave

(1)save 同步阻塞生成rdb文件 主要是在主程序执行,会阻塞当前redis服务器,直到持久化工作完成,执行save命令期间,redis不能处理其它命令,线上禁止使用

案例说明:


(2)bgsave 异步生成rdb文件(默认):redis会在后台异步快照操作,不阻塞 快照,同时还可以响应客户端请求,该触发方式会 fork一个子进程,由子进程复制持久化过程。

fork 是什么?

在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,尽量避免膨胀。

案例说明:


lastsave 可以通过lastsave命令获取最后一次成功执行快照的时间。

2)RDB 持久化优劣势

优势:

  1. RDB是一个紧凑压缩的二进制文件,效率高,Redis在某个时间点上的数据 快照。非常适用于备份,全量复制等场景。
  2. 与 AOF 格式的文件相比,RDB 文件可以更快的重启。
  3. RDB 对灾难恢复非常有用,它是一个紧凑的文件,可以更快的传输到远程服务器进行 Redis 服务恢复,性能最大化,fork出子进程来完成写操作,让主进程继续处理命令,所以是IO最大化(使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能) ;
  4. 如果数据集偏大,RDB的启动效率会比AOF更高。

劣势:

  • RDB方式数据没无法做到实时持久化,紧急情况丢失的数据比AOF多
  • bgsave每次执行都会fork新的进程,牺牲一部分cpu
  • Reids不同版本的RDB文件不统一,容易造成不兼容

数据丢失的案例:

  1. 正常录入数据
  2. kill -9 故意模拟意外宕机
  3. redis重启恢复后,查看数据是否丢失

3)哪些情况会触发RDB快照

4)如何禁用快照

方式一:使用命令设置保存rdb规则的方法:redis-cli config set save ""

方式二:配置文件,快照禁用

5)小总结

2.4 AOF持久化

1)持久化流程

命令写入 (append)、文件同步(sync)、文件重写(rewrite)、重启加载 (load)。

2)缓存区三种写回策略

# 每次有新命令追加到 AOF 文件时就执行一次同步 :非常慢,也非常安全$ always# 每秒同步一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据# 推荐(并且也是默认)的措施为每秒同步一次, 这种策略可以兼顾速度和安全性$ everysec # 从不同步:将数据交给操作系统来处理。更快,也更不安全的选择$ no

策略一:同步写回Always :每个写命令执行完立刻同步的将命令写回磁盘。
策略二:每秒写回everysec :每个写命令执行完,只是先把日志写到 AOF文件的内存缓冲区,每隔一秒把缓冲区的内容写入磁盘。
策略三:操作系统控制写回no :每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区的内容写入磁盘。

具体配置如下

3)AOF 持久化优劣势

优势:

  • AOF 持久化保存的数据更加完整,AOF 提供了三种保存策略:每次操作保存、每秒钟保存一次、跟随系统的持久化策略保存,其中每秒保存一次,从数据的安全性和性能两方面考虑是一个折中的选择,也是 AOF 默认的策略,即使发生了意外情况,最多只会丢失 1s 钟的数据;
  • AOF 采用的是命令追加的写入方式,所以不会出现文件损坏的问题,即使由于某些意外原因,导致了最后操作的持久化数据写入了一半,也可以通过 redis-check-aof 工具轻松的修复;
  • AOF 持久化文件,非常容易理解和解析,它是把所有 Redis 键值操作命令,以文件的方式存入了磁盘。即使不小心使用 flushall 命令删除了所有键值信息,只要使用 AOF 文件,删除最后的 flushall 命令,重启 Redis 即可恢复之前误删的数据。

劣势:

  • 相同数据集的数据而言,aof文件要远大于rdb文件,恢复速度慢于rdb
  • 在 Redis 负载比较高的情况下,RDB 比 AOF 性能更好;
  • RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因此从理论上说,RDB 比 AOF 更健壮。

4)AOF 重写机制

AOF 是存放每条写命令的,所以会不断变大,达到一定的时候,AOF做rewrite操作,会重新生成一个新的AOF文件。

文件越大,占用服务器内存越大以及 AOF 恢复要求时间越长。

为了解决这个问题,Redis新增了重写机制,当AOF文件的大小超过所设定的峰值时,Redis就会自动启动AOF文件的内容压缩,

简单说就是将对一个数据的多个命令的最终结束结果储存到AOF文件中。

重写 rewrite的作用

  • 降低磁盘占用率
  • 提高持久化效率,提高IO性能
  • 降低恢复用时

重写规则

  • 进程内已超时的数据不写入
  • 忽略无效指令,只保留最终结果,例如 set name 1, set name 2,只会保存第二条
  • 多条命令合并为1条,例如 del key1,del key2会被保存为 del key1 key2

手动重写

自动重写

  1. 在重写开始前,redis会创建一个“重写子进程”,这个子进程会读取现有的AOF文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。
  2. 与此同时,主进程会将新接收到的写指令一边累积到内存缓冲区中,一边继续写入到原有的AOF文件中,这样做是保证原有的AOF文件的可用性,避免在重写过程中出现意外。
  3. 当“重写子进程”完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新AOF文件中
  4. 当追加结束后,redis就会用新AOF文件来代替旧AOF文件,之后再有新的写指令,就都会追加到新的AOF文件中
  5. 重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似

5)小总结

2.5 RDB+AOF 混合持久化

RDB和AOF持久化各有优缺点,RDB会导致一段时间内的数据丢失,AOF文件会越来越大,会影响Redis的启动速度,为了同时兼顾RDB,AOF的优点,Redis在4.0版本之后提供了混合持久化方式。

AOF 重写时会把 Redis 的持久化数据,以 RDB 的格式写入到 AOF 文件的开头,之后的数据再以 AOF 的格式化追加的文件的末尾,如下图所示。

1)开启混合持久化

开启混合 将no改为yes即可

2)优缺点

优点:混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动,同时结合 AOF 的优点,有减低了大量数据丢失的风险。

缺点:AOF 文件中添加了 RDB 格式的内容,会使得 AOF 文件的可读性会很差,不容易阅读;

如果开启混合持久化,就必须使用 Redis 4.0 以及之后版本。

3)小总结

使用混合持久化的时候可以根据自身业务选择关闭RDB或者AOF,或者关闭持久化。

二者选择的标准,就是看是否愿意牺牲一些性能,换取更高的缓存一致性(AOF),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行 save 的时候,再做备份(RDB)。

注: 未来 Redis 可能会将 AOF 和 RDB 整合成单个持久化模型.

4. 常见面试题及解析

4.1 Redis 如何实现数据不丢失?

Redis 的读写操作都是在内存中,所以 Redis 性能才会高,但是当 Redis 重启后,内存中的数据就会丢失,那为了保证内存中的数据不会丢失,Redis 实现了数据持久化的机制,这个机制会把数据存储到磁盘,这样在 Redis 重启就能够从磁盘中恢复原有的数据。

Redis 共有三种数据持久化的方式:

  1. AOF 日志:每执行一条写操作命令,就把该命令以追加的方式写入内存缓冲区,然后刷新到aof文件里;
  2. RDB 快照:将某一时刻的内存数据,以二进制的方式写入磁盘;
  3. 混合持久化方式:Redis 4.0 新增的方式,集成了 AOF 和 RBD 的优点;

4.2 AOF 日志是如何实现的?

Redis 在执行完一条写操作命令后,就会把该命令以追加的方式写入到一个文件里,然后 Redis 重启时,会读取该文件记录的命令,然后逐一执行命令的方式来进行数据恢复。

我这里以「set name xiaolin」命令作为例子,Redis 执行了这条命令后,记录在 AOF 日志里的内容如下图:

我这里给大家解释下。
「*3」表示当前命令有三个部分,每部分都是以「$+数字」开头,后面紧跟着具体的命令、键或值。然后,这里的「数字」表示这部分中的命令、键或值一共有多少字节。例如,「$3 set」表示这部分有 3 个字节,也就是「set」命令这个字符串的长度。

4.3 Redis AOF 为什么先执行命令,再把数据写入日志呢?

Reids 是先执行写操作命令后,才将该命令记录到 AOF 日志里的,这么做其实有两个好处。

  1. 避免额外的检查开销:因为如果先将写操作命令记录到 AOF 日志里,再执行该命令的话,如果当前的命令语法有问题,那么如果不进行命令语法检查,该错误的命令记录到 AOF 日志里后,Redis 在使用日志恢复数据时,就可能会出错。
  2. 不会阻塞当前写操作命令的执行:因为当写操作命令执行成功后,才会将命令记录到 AOF 日志。

当然,这样做也会带来风险:

  1. 数据可能会丢失: 执行写操作命令和记录日志是两个过程,那当 Redis 在还没来得及将命令写入到硬盘时,服务器发生宕机了,这个数据就会有丢失的风险。
  2. 可能阻塞其他操作: 由于写操作命令执行成功后才记录到 AOF 日志,所以不会阻塞当前命令的执行,但因为 AOF 日志也是在主线程中执行,所以当 Redis 把日志文件写入磁盘的时候,还是会阻塞后续的操作无法执行。

4.4 Redis AOF 写回策略有几种?

先来看看,Redis 写入 AOF 日志的过程,如下图:
具体说说:

  1. Redis 执行完写操作命令后,会将命令追加到 server.aof_buf 缓冲区;
  2. 然后通过 write() 系统调用,将 aof_buf 缓冲区的数据写入到 AOF 文件,此时数据并没有写入到硬盘,而是拷贝到了内核缓冲区 page cache,等待内核将数据写入硬盘;
  3. 具体内核缓冲区的数据什么时候写入到硬盘,由内核决定。

Redis 提供了 3 种写回硬盘的策略,控制的就是上面说的第三步的过程。 在 Redis.conf 配置文件中的 appendfsync 配置项可以有以下 3 种参数可填:

  • Always,这个单词的意思是「总是」,所以它的意思是每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘;
  • Everysec,这个单词的意思是「每秒」,所以它的意思是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;
  • No,意味着不由 Redis 控制写回硬盘的时机,转交给操作系统控制写回的时机,也就是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。

我也把这 3 个写回策略的优缺点总结成了一张表格:

4.5 RDB 快照时如何实现的?

因为 AOF 日志记录的是操作命令,不是实际的数据,所以用 AOF 方法做故障恢复时,需要全量把日志都执行一遍,一旦 AOF 日志非常多,势必会造成 Redis 的恢复操作缓慢。

为了解决这个问题,Redis 增加了 RDB 快照。所谓的快照,就是记录某一个瞬间东西,比如当我们给风景拍照时,那一个瞬间的画面和信息就记录到了一张照片。

所以,RDB 快照就是记录某一个瞬间的内存数据,记录的是实际数据,而 AOF 文件记录的是命令操作的日志,而不是实际的数据。

因此在 Redis 恢复数据时, RDB 恢复数据的效率会比 AOF 高些,因为直接将 RDB 文件读入内存就可以,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。

4.6 RDB 做快照会阻塞线程么?

Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave,他们的区别就在于是否在「主线程」里执行:

  • 执行了 save 命令,就会在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程
  • 执行了 bgsave 命令,会创建一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞

Redis 还可以通过配置文件的选项来实现每隔一段时间自动执行一次 bgsave 命令,默认会提供以下配置:

// 900 秒之内,对数据库进行了至少 1 次修改;// 300 秒之内,对数据库进行了至少 10 次修改;// 60 秒之内,对数据库进行了至少 10000 次修改。save 900 1save 300 10save 60 10000

别看选项名叫 save,实际上执行的是 bgsave 命令,也就是会创建子进程来生成 RDB 快照文件。 只要满足上面条件的任意一个,就会执行 bgsave。

Redis 的快照是全量快照,也就是说每次执行快照,都是把内存中的「所有数据」都记录到磁盘中。所以执行快照是一个比较重的操作,如果频率太频繁,可能会对 Redis 性能产生影响。如果频率太低,服务器故障时,丢失的数据会更多。

4.7 RDB 在执行快照的时候,数据能修改吗?

可以的,执行 bgsave 过程中,Redis 依然可以继续处理操作命令的,也就是数据是能被修改的,关键的技术就在于写时复制技术(Copy-On-Write, COW)

执行 bgsave 命令的时候,会通过 fork() 创建子进程,此时子进程和父进程是共享同一片内存数据的,因为创建子进程的时候,会复制父进程的页表,但是页表指向的物理内存还是一个,此时如果主线程执行读操作,则主线程和 bgsave 子进程互相不影响。

如果主线程执行写操作,则被修改的数据会复制一份副本,然后 bgsave 子进程会把该副本数据写入 RDB 文件,在这个过程中,主线程仍然可以直接修改原来的数据。

4.8 为什么会有混合持久化?

RDB 优点是数据恢复速度快,但是快照的频率不好把握。频率太低,丢失的数据就会比较多,频率太高,就会影响性能。

AOF 优点是丢失数据少,但是数据恢复不快。

为了集成了两者的优点, Redis 4.0 提出了混合使用 AOF 日志和内存快照,也叫混合持久化,既保证了 Redis 重启速度,又降低数据丢失风险。

混合持久化工作在 AOF 日志重写过程,当开启了混合持久化时,在 AOF 重写日志时,fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

也就是说,使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据

这样的好处在于,重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样加载的时候速度会很快。

加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得数据更少的丢失

混合持久化优点:混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动,同时结合 AOF 的优点,有减低了大量数据丢失的风险。

混合持久化缺点

  • AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差;
  • 兼容性差,如果开启混合持久化,那么此混合持久化 AOF 文件,就不能用在 Redis 4.0 之前版本了。

5. 总结

5.1 AOF 和RDB 持久化对比