1. AOF(Append Only File)
首先学习的Redis版本:5.0.10
1.1 概念
AOF 是 Redis 的追加日志,采用‘先执行后记录’的方式,保证写入日志的命令都是成功执行的。它的优点是命令可读性好、不阻塞写操作;缺点是可能因未及时刷盘而丢失数据,且文件大、恢复慢。Redis 通过 AOF Rewrite 机制压缩日志体积。
🎯写回策略
- Always(同步写回),在执行完写命令后,Redis会立即将AOF日志数据同步写回磁盘,然后响应客户端成功,这种方式保证了最高的数据安全性,但性能开销比较大,每次都要等待磁盘I/O完成。
- Everysec(每秒写回),在执行完写命令后,Redis 会先把命令写入自己维护的 AOF 缓冲区,然后调用 write() 把数据推送到操作系统的 page cache,但并不会立即同步到磁盘,而是每隔一秒将缓冲区中的内容写入并同步到磁盘;在这一秒内,那些还未同步到磁盘的写操作可能会丢失;redis在将命令写入内存缓存区后会立即响应客户端成功。这种方式在性能和数据安全性做了折中的处理。
- No(操作系统控制写回),在执行完写命令后,Redis 会先把命令写入自己维护的 AOF 缓冲区,然后调用 write() 把数据推送到操作系统的 page cache,但何时同步到磁盘完全由操作系统控制,这种方式提供了很好的性能,然后如果redis突然奔溃,可能会丢失大量未同步的数据。
Redis 的 AOF 缓冲区(用户空间),可以理解为多攒一些命令,再调用write()系统调用。
Redis 命令执行
↓
写入 Redis 的 AOF 缓冲区(用户空间)
↓
调用 write() → 数据进入 Page Cache(内核空间)
↓
操作系统决定何时刷盘(fsync 被跳过)
策略 | Redis 行为 | 操作系统行为 | 数据丢失风险 | 性能 | 适用场景 |
---|---|---|---|---|---|
always | 每次 write() 后调用 fsync() | 立即刷盘 | 几乎为 0 | 最差 | 金融级安全 |
everysec | 每秒调用一次 fsync() | 每秒刷一次盘 | 最多丢 1 秒数据 | 平衡 | 默认,推荐 |
no | 从不调用 fsync() | 内核决定刷盘时机 | 可能丢几十秒数据 | 最好 | 高性能、可容忍丢失 |
内存类型 | 所属层级 | 用途 | 谁管理 |
---|---|---|---|
Redis 内存 | 用户空间(User Space) | 存储 key-value 数据、数据结构 | Redis 进程自己管理 |
Page Cache | 内核空间(Kernel Space) | 缓存文件的读写,加速磁盘 I/O | 操作系统内核管理 |
🎯配置
例如在Windows系统方面,文件名称:redis.windows.conf
appendonly no 关闭aof日志
appendonly yes 开启aof日志
appendfsync everysec 选择写回策略
appendfilename "appendonly.aof" 指定aof文件名称
🎯日志记录形式
例如我在redis客户端执行set name 566
,在appendonly.aof
文件中会记录下面形式的内容:
*3 //表示当前命令有三个部分
$3 //表示后面紧跟着的命令、键或者值有多少个字节
set //占用三个字节
$4
name //占用四个字节
$3
566 //占用三个字节
1.2 AOF重写日志
当AOF文件越来越大的时候:
- 一是文件系统本身对文件大小有限制;
- 二是文件太大追加命令记录效率也会变低;
- 三是重启Redis用AOF文件来恢复数据,文件太大也会非常缓慢。
例如我们对一个key反反复复操作几百万次,日志会产生大量的内容,在重写的时候,只需要根据这个key当前的最新状态,为它生成对应的写入命令,这样日志就完成了大量的缩减。
🎯自动触发重写
AOF(Append-Only File)重写是 Redis 用于优化持久化文件体积的核心机制,旨在消除冗余写命令、压缩日志大小,从而提升存储效率与系统恢复性能。该机制支持自动触发与手动触发两种方式。
Redis 可根据配置策略自动判断是否执行 AOF 重写。触发条件由以下两个关键参数共同控制:
auto-aof-rewrite-percentage
:表示当前 AOF 文件相对于上一次重写后体积的增长百分比阈值。
auto-aof-rewrite-min-size
:表示触发重写的 AOF 文件最小容量阈值(避免过小文件频繁重写)。
示例:
设 auto-aof-rewrite-percentage = 100,auto-aof-rewrite-min-size = 64mb。
若上一次重写后文件大小为 64MB,当前增长至 128MB,则增长率为 100%,且超过最小阈值,满足双重条件,Redis 将自动启动 BGREWRITEAOF 进程。
🎯手动触发重写
可通过客户端显式执行以下命令,立即发起 AOF 重写请求:
BGREWRITEAOF
该命令会启动一个子进程,基于当前数据库状态生成精简版的 AOF 文件。主进程继续处理请求,保证服务不中断,体现了 Redis 的非阻塞持久化设计哲学。
- Background:表示该操作在后台(即子进程)中执行,不会阻塞 Redis 主进程处理客户端请求。
- Rewrite:表示对 AOF 文件进行重写,即重新生成一个等效但更精简的 AOF 文件。
- Append-Only File:Redis 的持久化机制之一,所有写操作都以追加方式写入日志文件。
🎯重写过程
Redis 的 AOF 重写通过 fork 子进程 + 写时复制(Copy-on-Write, COW)+ 重写缓冲区 三者协同,实现了不阻塞主进程的前提下完成日志压缩,整个过程安全高效,具体流程如下:
- **触发重写:**当满足自动策略(如 aof-rewrite-percentage 和 min-size)或手动执行 BGREWRITEAOF 命令时,Redis 主进程决定启动 AOF 重写。
- **fork 子进程:**主进程调用 fork() 创建一个子进程。子进程继承主进程的内存数据副本,得益于操作系统的 写时复制(COW)机制,此时并未真正复制内存,仅共享页表,资源开销极小。
- **子进程生成新 AOF:**子进程遍历当前数据库的所有键值对,将每个 key 的当前状态转换为一条等效的写命令(如 SET key value、HSET hash f v),并写入一个临时的 AOF 文件(如 temp-rewriteaof-bg-*.aof)。
- **主进程继续服务,记录增量:**在子进程重写期间,主进程正常处理客户端读写请求。对于所有新的写操作,继续追加日志到 旧的 AOF 文件,Redis 会额外将这些命令追加到一个内存缓冲区——AOF 重写缓冲区(server.aof_rewrite_buf_blocks),用于记录重写期间的增量变更。
- **同步增量数据:**子进程完成基础数据的写入后,并不会立即结束。它会从主进程的重写缓冲区中读取所有累积的写命令,并追加到临时 AOF 文件末尾,确保新文件包含重写期间的所有变更。
- **原子切换文件:**子进程通知主进程“重写完成”。主进程将重写缓冲区中最后可能新增的少量命令再次同步到临时文件,确保数据完全一致。随后,主进程使用 rename 系统调用原子地用新 AOF 文件替换旧文件,并关闭旧文件描述符。
- **清理与完成:**旧 AOF 文件被操作系统自动回收,Redis 后续的写操作将追加到新的、更精简的 AOF 文件中。
🎯写时复制
子进程看到的内存数据是 fork() 那一刻的“快照”,即使主进程后续修改了数据,子进程也不会受到影响。这得益于操作系统提供的 写时复制(Copy-on-Write)机制。
在 fork() 完成后,父子进程共享同一份物理内存页(只读)。一旦主进程对某个内存页进行写操作(如修改一个 key),操作系统就会:
- 拷贝这个内存页
- 让主进程写入新的副本
- 子进程仍然读取原始的旧页
- 这样,子进程看到的数据就“冻结”在了 fork() 的那一刻。
🎯AOF重写过程中的风险问题
主进程会持续将写命令追加到旧的 AOF 文件中。如果新文件因故障未完成,Redis 启动时会检测到其不完整,并自动使用旧的 AOF 文件恢复数据。只有在极端情况下(如磁盘损坏 + 配置不当)才可能丢失极少量数据。
Redis 的 AOF 重写不是“覆盖”,而是“生成+原子切换”,并始终保留旧文件作为故障恢复的兜底保障。因此,在绝大多数异常场景下,数据不会丢失。
2. RDB
RDB,是Redis DataBase的缩写,和AOF相比,RDB记录的是某一时刻的数据,并不是操作。所以在做数据恢复的时候,可以直接把RDB文件读入内存,很快地完成恢复。
2.1 写回策略
🎯save 指令
save 指令,在配置文件中使用 save 指令设定多个时间/变更次数组合。含义:如果在 900 秒内至少有 1 个键发生变化,则触发 RDB;或者在 300 秒内至少有 10 个键发生变化,依此类推。
一旦某个 save 规则被触发(例如 save 300 10 在 300 秒内发生了 10 次写操作),Redis 会执行 BGSAVE,然后重置计数器和计时器**,从头开始新一轮的统计。
如果想要关闭RDB,就把这些命令给注释了。
save 900 1
save 300 10
save 60 10000
当满足上述任何一个条件时,Redis 并不会直接调用 SAVE 命令,而是会调用 BGSAVE 命令。这样做的目的是为了避免在生成 RDB 文件的过程中阻塞 Redis 主进程,从而确保服务的可用性。
🎯收到触发
使用 SAVE 命令立即同步生成 RDB 文件,但此操作会阻塞 Redis 主进程,直到 RDB 文件创建完成。
使用 BGSAVE 命令异步生成 RDB 文件,允许 Redis 主进程继续处理客户端请求,适合生产环境使用。
2.2 bgsave过程
- fork()子进程;
- 父进程和子进程会共享相同的物理页;
- 写时复制,当客户端对父进程发送了写请求,操作系统会为修改的这个页面的进程复制该页面的副本,这样修改就被隔离在各自的进程空间中;
- 子进程遍历Redis内存中的键值对数据,并将写入新的RDB文件中,由于写时复制机制,即使父进程在子进程生成 RDB 文件期间修改了内存中的数据,这些修改也不会影响到子进程正在写入的 RDB 文件。子进程写入的是 fork() 时刻的内存数据快照,而不是实时数据。
- 当子进程将所有键值对都写入 RDB 文件后,它会用新的 RDB 文件替换旧的 RDB 文件。
所以子进程是遍历它看到的内存数据(即 fork 时刻的状态),生成 RDB 文件,新 RDB 文件反映的是 fork() 那一刻的数据,不包含之后的任何写入。
但要注意:“新写入的数据”不是“丢失”,这些在 BGSAVE 过程中新增或修改的数据:
- 如果启用了 AOF,会被追加到 AOF 文件中 ✅
- 如果没启用 AOF,那么它们只存在于内存中,直到下一次 BGSAVE 或 SAVE
🎯bgsave的性能隐患
- fork子进程这个创建过程会阻塞主线程;
- 写时复制,意味着bgsave阶段,客户端如果发生大量的写请求,会复制大量的内存数据,内存占用的问题。
3. AOF+RDB混合模式
“丢失 1 个命令” 的准确含义是:“在最坏情况下,客户端无法确认最后一个命令是否成功提交”,即使它可能已经在磁盘上。这属于 “感知层面的不确定性”,而不是“数据物理丢失”。
场景 | 数据安全 |
---|---|
只用 RDB(如 save 300 10 ) | 最多丢失最后一次快照之后 5 分钟的数据 |
启用 AOF | 最多丢失 1 秒(everysec 模式)或 1 个命令(always 模式) |
RDB + AOF 混用 | 重启时优先用 AOF 恢复,几乎不丢数据 |
3.1 概念
从 Redis 4.0 开始,引入了 AOF + RDB 混合持久化机制,默认开启混合模式。
在混合模式下,AOF 文件的前半部分是一个紧凑的 RDB 格式的数据快照,后半部分则是标准的 AOF 命令流。具体来说:
- RDB 部分:包含了某个时间点上的完整数据集,类似于单独使用 RDB 时生成的快照文件。
- AOF 部分:从 RDB 快照之后的所有写操作都被追加到文件末尾,采用标准的 AOF 格式。
REDIS0009?redis-ver5.0.10?redis-bits繞?ctime篓蓵h?used-mem??aof-preamble?T-?磛l?2
$6
SELECT
$1
0
*3
$3
set
$4
name
$2
gt
🎯配置
即使你把配置文件中所有的save指令都注释掉,也不会影响混合模式。混合模式下,当发生AOF日志重写的时候,子进程会基于当前内存的数据生成一个新的RDB快照。
# 启用 AOF 持久化
appendonly yes
# 设置 AOF 文件格式为混合模式
aof-use-rdb-preamble yes
3.2 混合模式下的AOF重写日志
- 当你手动执行 BGREWRITEAOF 或者 Redis 根据配置自动触发时,Redis 会启动一个子进程来进行 AOF 文件的重写。
- 子进程生成新的 AOF 文件:这个新生成的 AOF 文件将包含两个主要部分:子进程基于当前内存中的数据生成一个新的 RDB 快照。紧跟在 RDB 快照之后的是自该快照创建以来的所有写操作命令,这些命令是以标准的 AOF 格式记录的。
- 主进程持续写命令 → 追加到旧 AOF 文件 + 写入重写缓冲区
- 子进程完成 → 发送 SIGCHLD 信号给主进程
- 主进程收到信号 → 将重写缓冲区内容追加到新的AOF文件末尾
- 主进程执行 rename(temp-..., appendonly.aof) → 原子替换
- 主进程继续将新命令写入新的 appendonly.aof