type
status
date
slug
summary
tags
category
password
1、概述
Redis是一个内存数据库,所有的数据将保存在内存中,这与传统的 MySQL、Oracle、SqlServer 等关系型数据库直接把数据保存到硬盘相比,Redis的读写效率非常高。但是保存在内存中也有一个很大的缺陷,一旦断电或者宕机,内存数据库中的内容将会全部丢失。为了弥补这一缺陷,Redis 提供了把内存数据持久化到硬盘文件,以及通过备份文件来恢复数据的功能,即 Redis 的持久化机制。有两种方式:
持久化方式 | 描述 | 优点 | 缺点 | 适合场景 |
AOF | 记录所有写操作命令,以追加方式写入日志文件,重启时重新执行这些命令来恢复数据 | 数据安全性高,使用 everysec 策略最多丢失一秒数据,使用 always 策略则几乎不会丢失数据。 | 文件体积大,数据恢复速度慢 | 文件小,恢复速度快,适合用于定时快照备份和主从全量数据同步的场景。 |
RDB | 按照指定时间间隔生成 Redis 数据库快照( dump.rdb 文件),这些快照保存了某个时间点的全量 Redis 数据。可以使用数据库快照来恢复数据或者实现主从复制。 | 二进制文件体积小,恢复速度快,便于快速备份和灾难恢复 | 数据安全性低,如果 Redis 意外宕机,从上一次快照到宕机时的数据都会丢失。 | 文件大,但是大部分情况保留了原始的命令操作,适合用于对数据完整性和安全性更高的场景中。 |
2、AOF
AOF 持久化方式会记录每一次写操作命令,并将这些命令追加到一个日志文件的末尾。当 Redis 重启时,会重新执行 AOF 文件中的所有命令来重建数据。
注意只会记录写操作命令,读操作命令是不会被记录的。
2.1 开启AOF
第一种方式,通过配置文件开启(推荐),修改 Redis 配置文件
redis.conf
:第二种方式,可以通过 Redis 客户端临时开启 AOF(重启后会失效):
AOF 文件默认保存在 Redis 的工作目录(由
dir
配置指定),文件名为 appendonly.aof
。2.2 AOF的工作原理
AOF 的操作步骤为:
- 命令追加(append):当 AOF 持久化功能打开时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器的 AOF 缓冲区的末尾。(先写内存,再写日志。这样可以保证只有正确执行的命令才会记录到日志上。)
- 文件的写入和同步(sync):根据配置的
appendfsync
同步刷盘策略决定何时将 AOF 缓冲区的内容保存到磁盘的 AOF 文件中(原理是调用 Linux 的fsync
或fdatasync
函数)。
AOF 提供三种同步策略(通过
appendfsync
配置):配置项 | 写回时机 | 优点 | 缺点 |
Always | 每次执行完一个命令之后, write 和 sync 都会被执行。也就是说每个写命令都会同步写入磁盘 | 可靠性高,数据基本不丢失 | 性能较差 |
Everysec | 默认策略,write 和 sync 每隔一秒钟就会执行一次。也就是说每秒会同步一次磁盘,该操作由一个线程专门负责。 | 安全性和性能的折中方案 | 宕机丢失1s的数据 |
No | write 都会被执行, 但 sync 会被略过。也就是说将 aof_buf 缓存区的内容写入 AOF 文件,但什么时候同步到磁盘由操作系统来决定 | 性能好 | 宕机丢失数据多 |
2.2.1 AOF缓冲区
AOF 缓冲区是 Redis 服务器中的一个内存缓冲区。当 Redis 执行完一个写命令后,这个命令会按照 AOF 协议格式被追加到这个缓冲区中。
AOF 缓冲区主要目的是解决性能问题:如果每执行一个写命令就立即将其写入磁盘的 AOF 文件,那么磁盘 I/O 操作会非常频繁,严重拖累 Redis 的性能(Redis 的核心优势就是基于内存的高速读写)。AOF 缓冲区作为一个中间层,先将命令在内存中积累起来,然后根据配置的
appendfsync
策略,统一、批量地写入磁盘,大大减少了磁盘 I/O 次数。2.3 AOF重写机制
AOF 日志是一个文件,随着执行的写操作命令越来越多,文件的大小会越来越大。如果当 AOF 日志文件过大就会带来性能问题,比如重启 Redis 后,需要读 AOF 文件的内容以恢复数据,如果文件过大,整个恢复的过程就会很慢。
假设你对一个计数器
mycounter
执行了 100 次 INCR
命令。- 原始的 AOF 文件会忠实地记录这 100 条
INCR mycounter
命令。
- 最终在数据库里,
mycounter
的值是 100。
- 但事实上,要达到这个状态,我们只需要一条命令:
SET mycounter 100
。
为了解决 AOF 文件体积膨胀的问题,Redis 提供了 AOF 重写机制。当 AOF 文件的大小超过所设定的阈值后,Redis 就会启用 AOF 重写机制,来压缩 AOF 文件。
AOF 重写的触发条件:
- 手动触发:执行
BGREWRITEAOF
命令
- 自动触发:在
redis.conf
配置文件中进行设置,当满足以下两个条件时,Redis 会自动触发BGREWRITEAOF
。下面的配置意味着:如果 AOF 文件已经大于 64MB,并且其当前大小比上次重写后的大小至少大了一倍(100%),那么就会自动触发重写。
AOF 重写过程如下:
- 主线程
fork
出一个 bgrewriteaof 子进程,这里的fork()
使用写时拷贝(copy-on-write)的技术,拷贝出一份数据副本,并建立一个 AOF 重写缓冲区。
- 子进程读取当前数据库数据的所有键值对的最新值,生成新的 AOF 文件。注意:AOF 重写不会去读旧的 AOF 文件,而是读取当前数据库中的所有键值对,然后为每一个键值生成一条命令记录到新的 AOF 文件中。

- 子线程重写期间,如果服务器收到了写命令,主线程会先把写命令写到原有的 AOF 缓冲区(这部分数据会写到旧的 AOF 文件,这样保证即使重写失败也能保证数据的安全),然后再把这些命令写入上面创建的 AOF 重写缓冲区。
- 子进程重写完成后,通知主线程,主线程收到信号后会把 AOF 重写缓冲区的内容写到新的 AOF 文件(不会直接写到新的 AOF 文件里面,而是先写 AOF 缓冲区,再写回新的 AOF 文件)
- 最后主线程把新的 AOF 文件进行改名,覆盖原有的 AOF 文件,之后所有的写命令都会开始追加到这个新文件上。
整个过程只有最后两步是阻塞主线程的,这将 AOF 重写对性能造成的影响降到了最低。

这里涉及到两个知识点:
- AOF 重写缓冲区
- 写时复制(Copy-On-Write)技术
2.3.1 AOF重写缓冲区
AOF 重写缓冲区也是一个内存缓冲区。它是在 Redis 启动 AOF 重写(
BGREWRITEAOF
) 子进程时才被创建的。在子进程创建并生成新 AOF 文件的过程中,主进程还在持续接收和处理新的写命令。这会导致子进程的内存快照(fork 时刻的数据库状态)和当前数据库的实际状态不一致。所以 AOF 重写缓冲区就是为解决这个问题而生的。
- 在重写 AOF 期间,当 Redis 执行完一个写命令之后,除了会将写命令写入到 AOF 缓冲区(保证原来的 AOF 文件正常记录), 还会将命令同时写入 AOF 重写缓冲区。
- 在子进程重写完 AOF 文件后,会向主进程发送一个信号。主进程收到信号后,会将 AOF 重写缓冲区 中的所有内容追加到子进程生成的新 AOF 文件的末尾。这样,新 AOF 文件就包含了重写开始时刻的快照数据 + 重写过程中产生的增量数据,保证了数据的完整性。
AOF 缓冲区和 AOF 重写缓冲区对比
特性 | AOF 缓冲区 | AOF 重写缓冲区 |
目的 | 平衡性能与持久化,减少磁盘 I/O 次数。 | 保证重写期间的数据一致性,防止新数据丢失。 |
创建时机 | Redis 服务器启动时(如果开启了 AOF)。 | AOF 重写开始时(执行 BGREWRITEAOF 时)。 |
写入时机 | 任何时候执行写命令后。 | 仅在 AOF 重写过程中执行写命令后。 |
内容 | 所有写命令的协议文本。 | 重写过程中产生的所有写命令的协议文本。 |
清空时机 | 内容被同步到主 AOF 文件后。 | 内容被追加到新的重写 AOF 文件后。 |
生命周期 | 长期存在。 | 临时存在,仅在重写期间存在。 |
2.3.2 写时复制(Copy-On-Write)技术
写时复制技术(COW)的核心思想是:如果有多个调用者同时请求相同资源,他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给该调用者,而其他调用者所见到的资源仍然保持不变。简单来说:只有在真正需要写入的时候,才进行复制。
Redis 是一个单线程的内存数据库(主要工作线程是单线程的)。这意味着所有客户端的命令都必须在这个独享的线程中顺序执行。
如果有一个非常耗时的操作在这个主线程中运行,比如需要生成一个巨大的 RDB 快照文件(持久化),或者需要加载一个巨大的 AOF 文件,那么主线程就会被阻塞,无法处理任何新的命令,这对于一个高性能数据库来说是灾难性的。
为了解决这个问题,Redis 的 RDB 持久化和 AOF 重写都是在后台子进程中执行的。
BGSAVE
: 在后台子进程中创建当前数据集的 RDB 快照。
BGREWRITEAOF
: 在后台子进程中重写 AOF 文件以减小体积。
这里就产生了一个问题:主进程在不断地处理新的读写命令,内存数据在不断变化,而子进程需要为某一时刻的数据生成一个快照。如何保证快照的一致性?
解决方案就是 COW 机制 + fork()。
fork()
子线程:Redis 使用操作系统的fork()
系统调用来创建后台子进程。fork()
的作用是创建一个与父进程完全相同的子进程。在fork()
之后,内核并不会立即复制父进程的整个内存空间给子进程。相反,它会将父进程的物理内存页标记为只读(read-only),并让父子进程共享这些内存页。
- 触发异常:当父进程(处理客户端请求的主进程)接收到一个写命令(如
SET
,DEL
等),试图修改某一块共享的内存数据时:,CPU 硬件检测到内存页是只读的,于是触发页异常中断(page-fault)。
- 内核介入:操作系统内核的页错误处理程序会介入。
- 复制页面:内核会将被修改的内存页(通常是 4KB 大小)复制一份新的副本。
- 父进程写入新数据:将父进程的页表项指向这个新复制的页面,并标记为可写。父进程的写操作在这个新页面上继续进行,修改成功。
- 子进程读取旧数据:而此时,子进程的页表项仍然指向旧的、未被修改的内存页面。子进程看到的数据就像是
fork()
发生那一刻的静态快照。所以子线程备份的是旧数据。

举个例子:
假设 Redis 内存中有一个很大的
Hash
表。当子进程通过 fork()
创建时,父子进程共享这个 Hash
表。- 如果父进程没有收到任何修改这个
Hash
的命令,子进程就能一直“读”这个共享的Hash
表,并把它写入 RDB 文件,零复制成本。
- 如果父进程要修改这个
Hash
表中的某个字段,那么仅会复制这个字段所在的具体内存页(4KB),而不是整个Hash
表,更不是整个数据库。修改完成后,子进程依然看到的是旧的数据。
优势:
- 高效节省内存:理论上,子进程不需要消耗与父进程一样大的内存。它只在父进程有写入操作时,才消耗额外的内存(复制的页面)。如果父进程几乎没有写入,那么子进程的内存消耗就非常小。
- 高性能:
fork()
本身通常非常快,因为它不需要复制整个地址空间。这使得创建后台子进程的成本很低,Redis 可以频繁地执行bgsave
或bgrewriteaof
而不会对性能造成太大影响。
- 保证快照一致性:子进程中的数据是
fork()
那一刻的完美快照,保证了 RDB 文件的数据一致性。
缺点:
- 写操作可能比传统方式稍慢(需要先复制)
- 父线程如果产生大量写操作,会产生大量的页中断异常,也会造成性能损失。
3、RDB
RDB 通过创建某个时间点的数据快照(RDB 文件)来实现持久化。RDB 文件是压缩的二进制文件,默认名为
dump.rdb
,保存着 Redis 某个时间点的全量数据。注意:执行快照是一个比较重的操作,如果频率太频繁,可能会对 Redis 性能产生影响。如果频率太低,服务器故障时,丢失的数据会更多。通常可能设置至少 5 分钟才保存一次快照,这时如果 Redis 出现宕机等情况,则意味着最多可能丢失 5 分钟数据。
3.1 开启RDB
- 自动触发:通过修改
redis.conf
配置文件自动触发。底层通过服务器周期函数 serverCron 函数来触发,该函数每 100 毫秒执行一次。默认配置如下所示:
- 手动触发:使用 Redis 命令手动触发。Redis 提供了两个命令来生成 RDB 文件,区别就在于是否在「主线程」里执行:
SAVE
命令:Redis 主进程负责创建 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程。BGSAVE
命令:Redis 主进程会 fork 出一个子进程,由子进程来创建 RDB 文件,这样可以避免主线程的阻塞。
验证 RDB 是否工作
在输出中查找与 RDB 相关的信息,如:
rdb_last_save_time
:上次成功保存的时间戳
rdb_last_bgsave_status
:上次后台保存状态
3.2 RDB的工作过程
以最常用的
BGSAVE
命令为例,执行过程如下:- fork 子进程:主线程 fork 出一个
BGSAVE
子进程,这里的fork()
同样使用写时复制(COW)的技术。这意味着父子进程最初共享相同的内存页,只有当父进程(处理写请求)要修改某一块数据时,操作系统才会将该数据页复制一份给子进程使用。。
- 子进程负责写入:子进程开始将共享的内存数据写入一个临时的 RDB 文件(例如
temp-<pid>.rdb
)。
- 替换旧文件:当子进程完成对所有数据的写操作后,会用这个新的临时 RDB 文件原子地替换掉旧的
dump.rdb
文件。
- 发送信号并退出:子进程完成后通知父进程,然后自身退出。
整个过程只有
fork
阶段会阻塞父进程(通常很短),而将数据写入磁盘的繁重工作由子进程完成,确保了主进程的高性能。不管是自动触发还是手动触发创建 RDB 文件,最终都会调用
rdbSave
函数来创建 RDB 文件。
注意:
- 为了避免产生竞争条件,
BGSAVE
执行时,SAVE
命令不能执行。
- 为了避免性能问题,
BGSAVE
和BGREWRITEAOF
不能同时执行。
- RDB 文件的加载工作是在服务器启动时自动执行的,Redis 并没有提供专门用于加载 RDB 文件的命令。
- 调用 rdbLoad 函数载入 RDB 文件时,不能进行任何和数据库相关的操作,不过订阅与发布方面的命令可以正常执行,因为它们和数据库不相关联。
- 如果服务器在启动时, 打开了 AOF 功能, 那么程序优先使用 AOF 文件来还原数据。 只有在 AOF 功能未打开的情况下, Redis 才会使用 RDB 文件来还原数据。这是因为在服务器发生故障时,RDB 丢失的数据会比 AOF 持久化的方式更多,因为 RDB 快照是全量快照的方式,因此执行的频率不能太频繁,否则会影响 Redis 性能,而 AOF 日志可以以秒级的方式记录操作命令,所以丢失的数据就相对更少。
4、混合模式(RDB + AOF)
还有一种混合模式:同时开启 AOF 和 RDB 功能。这是 Redis 4.0 之后引入的一个强烈推荐的选项。它结合了 RDB 和 AOF 的优点:
- 创建 RDB 快照文件以一定的频率执行。
- 在两次创建 RDB 文件期间,使用 AOF 日志记录这期间的所有命令操作。
- 在下一次创建 RDB 的时候,清空 AOF 日志。
- 在恢复数据的时候,会先加载 RDB 文件的内容(速度非常快),然后再重放增量 AOF 命令。大大提升了重启效率。

要开启混合持久化,需要在配置文件中设置:
- Author:mcbilla
- URL:http://mcbilla.com/article/ddfadf35-f56a-4957-9c5b-3531c46682f1
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts