当前位置: 代码网 > it编程>数据库>Redis > Redis 持久化与事务:RDB、AOF、MULTI/EXEC 与 WATCH 全解析

Redis 持久化与事务:RDB、AOF、MULTI/EXEC 与 WATCH 全解析

2026年09月30日 • Redis •我要评论
redis 持久化有 ‌rdb(快照)‌ 和 ‌aof(追加日志)‌ 两种方式,生产环境建议开启 ‌aof(默认 everysec 刷盘)‌

redis 持久化有 ‌rdb(快照)‌ 和 ‌aof(追加日志)‌ 两种方式,生产环境建议开启 ‌aof(默认 everysec 刷盘)‌,追求极致恢复速度时可开启 ‌混合持久化‌。redis 事务 ‌不是真正的 acid 事务‌,它只保证命令按顺序批量执行且执行期间不被其他客户端插队,‌不支持回滚‌,运行时错误不会终止后续命令。如果你需要强原子性,更推荐用 ‌lua 脚本‌。

1. redis持久化机制(rdb与aof)

redis之所以快,是因为数据都在内存中
但为了防止进程崩溃或宕机导致数据丢失,必须将数据落盘,这就是持久化

1.1. rdb(redis database):内存快照

rdb是将当前进程的数据生成快照保存到硬盘的过程,生成的是紧凑压缩的二进制文件

1.1.1 触发机制

手动触发:

  • save:阻塞当前redis服务器,直到rdb完成。基本废弃
  • bgsave:redis进程执行fork创建子进程,由子进程负责持久化,父进程阻塞仅发生在fork阶段。主流方式

自动触发:

  • 配置 save m n(m秒内发生n次修改则触发)
  • 主从全量复制时,主节点自动执行bgsave
  • 执行 shutdown 命令关闭redis时

1.1.2 bgsave 运作流程

  1. 父进程判断当前是否存在正在执行的子进程(如rdb/aof子进程),存在则直接返回
  2. 父进程执行 fork 创建子进程(此阶段父进程阻塞,可通过 info stats 的 latest_fork_usec 查看耗时微秒数)
  3. 父进程 fork 完成后,返回 "background saving started",不再阻塞,继续响应其他命令
  4. 子进程根据父进程内存快照(copy-on-write,写时复制机制)生成临时rdb文件,完成后对原有文件进行原子替换
  5. 子进程发送信号给父进程,父进程更新统计信息(rdb_last_save_time)
  • fork时父进程在干什么?

父进程继续响应其他命令。利用linux的写时复制机制,子进程共享父进程的内存页,只有当父进程修改数据时,才会复制一份新的内存页。这保证了极低的性能损耗

1.1.3 优缺点

  • 优点:文件紧凑,适合备份和全量复制;恢复速度快(远快于aof)
  • 缺点:无法做到实时/秒级持久化(fork是重量级操作,频繁执行成本高);版本演进兼容性有风险(特定二进制格式)

1.1.4 rdb文件的处理

  • 保存:rdb 文件默认保存到 dir 配置指定的目录(默认 /var/lib/redis/),文件名由 dbfilename 指定(默认 dump.rdb),运行期间可通过 config set dir {newdir} 和 config set dbfilename {newfilename} 动态修改,下次 rdb 持久化时会保存到新目录并使用新文件名
  • 压缩:redis 默认使用 lzf 算法压缩 rdb 文件,默认开启,可通过 config set rdbcompression {yes|no} 动态修改;压缩虽然会消耗一定 cpu,但能大幅减小文件体积,便于硬盘保存和主从节点网络传输,因此建议保持开启
  • 校验:redis 启动时如果加载到损坏的 rdb 文件会拒绝启动,此时可使用 redis-check-dump(新版本中为 redis-check-rdb)检测 rdb 文件并获取错误报告,生产环境应定期校验备份文件,发现损坏时优先从备份或从节点恢复

1.2. aof(append only file):日志追加

aof以独立日志的方式记录每次写命令,重启时重新执行aof文件中的命令来恢复数据
目前是redis持久化的主流方式

开启 aof 功能需要设置配置:appendonly yes,默认不开启。aof 文件名通过 appendfilename 配置(默认是 appendonly.aof)设置。保存目录同 rdb 持久化方式一致,通过 dir 配置指定

1.2.1 工作流程(append -> sync -> rewrite -> load)

  1. 命令写入:所有写命令追加到 aof_buf(缓冲区)中
  2. 文件同步:根据策略将缓冲区同步到硬盘
  3. 文件重写:定期压缩aof文件
  4. 重启加载:启动时加载aof文件恢复数据
  • 为什么要有 aof_buf 缓冲区

redis是单线程响应命令,如果每次都直接同步硬盘(io操作),性能会急剧下降。先写缓冲区可以减少io次数,同时允许提供多种同步策略来平衡性能与安全

1.2.2 文件同步策略(appendfsync)

redis 提供了多种 aof 缓冲区同步文件策略,由参数 appendfsync 控制

appendfsync 配置值说明fsync 时机建议
always命令写入 aof_buf 后调用 fsync 同步,完成后返回每次写入都执行 fsync除非是非常重要的数据,否则不建议配置
everysec命令写入 aof_buf 后只执行 write,不立即 fsync;每秒由同步线程执行一次 fsync每秒同步一次默认配置,也是推荐配置
no命令写入 aof_buf 后只执行 write,由操作系统控制 fsync 频率由 os 控制,不可控除非数据重要程度很低,一般不建议配置
  • always:每次写入都fsync。最安全,性能最差(sata硬盘仅支持几百tps)
  • everysec:每秒由同步线程fsync一次。默认且推荐配置,兼顾性能与安全,理论上最多丢失1秒数据
  • no:由操作系统控制fsync频率。性能最高,但数据丢失风险大
对比项write 操作fsync 操作
核心机制延迟写(delayed write),利用 linux 内核页缓冲区强制硬盘同步
操作对象文件描述符 / 文件单个文件
返回时机写入系统缓冲区后立即返回阻塞直到数据写入硬盘
是否阻塞通常不阻塞阻塞
落盘保证不保证立即落盘,依赖系统调度强制同步,等待落盘完成
同步触发缓冲区页空间写满、达到特定时间周期等显式调用触发
故障风险同步前系统故障宕机,缓冲区内数据可能丢失调用返回后,正常情况下数据已写入硬盘
性能影响性能高,减少磁盘 io性能开销大,增加延迟
适用场景常规写入,追求吞吐量需要持久化保证的关键数据

1.2.3 aof重写机制

随着命令不断写入,aof文件会越来越大,redis引入重写机制来压缩体积

  • 为什么能变小: 超时数据不再写入;无效命令(如被覆盖的set)被删除;多条写操作合并(如多次lpush合并为一条)
  • 触发时机:手动执行 bgrewriteaof,或根据 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 自动触发
配置项说明默认值
auto-aof-rewrite-min-size表示触发重写时 aof 的最小文件大小64mb
auto-aof-rewrite-percentage表示当前 aof 占用大小相比较上次重写时增加的百分比—

aof重写流程与并发细节

  1. 执行重写请求
    如果当前进程正在执行 aof 重写,请求不执行;
    如果当前有bgsave在执行,则延迟到其完成后再执行
  2. 父进程 fork 创建子进程
  3. 关键并发处理:
    • 父进程继续响应其他命令。所有修改操作写入 aof_buf,并根据策略同步到旧aof文件,保证旧aof机制正确
    • 由于子进程只有 fork 之前的内存信息,父进程需要将 fork 之后的修改操作写入 aof_rewrite_buf(aof重写缓冲区)
  4. 子进程根据内存快照,将命令合并写入新的aof文件
  5. 子进程完成后发送信号给父进程
  6. 父进程将 aof_rewrite_buf 中的命令追加到新aof文件,并用新aof文件原子替换老aof文件

1.2.4 启动时数据恢复

当redis启动时,如果同时存在rdb和aof文件,优先加载aof(因为aof数据更全)。如果aof不存在,则加载rdb。如果加载损坏的文件,redis会拒绝启动(可用 redis-check-aof 或 redis-check-dump 修复)

  • redis 4.0 的混合持久化

通过 aof-use-rdb-preamble yes 开启。开启后,aof 重写时子进程先将当前内存数据以 rdb 格式写入新 aof 文件头部;重写期间及之后的新写命令以 aof 格式追加到文件后面。重启加载时,先加载 rdb 头部,再重放后续 aof 命令。这样既利用 rdb 加载快、体积小的优点,又保留 aof 的增量记录,降低数据丢失风险。但数据丢失窗口仍取决于 appendfsync 策略,且混合 aof 文件对旧版本 redis 和部分工具兼容性较差

2. redis事务与乐观锁

redis的事务与mysql的事务概念类似,都是将一系列操作绑定成一组批量执行,但在acid特性上有极大的弱化

2.1. redis事务的acid特性

  • 弱化的原子性:redis没有“回滚机制”。只能做到“批量执行”,不能做到“一个失败就恢复到初始状态”。如果事务中某条命令执行失败(如类型错误),其余命令仍会继续执行
  • 满足一致性:无约束,不存在违反约束的非法状态。中间状态非法,需要业务自己判断
  • 天然隔离性:redis单线程处理请求,天然串行执行,不存在并发执行事务的问题
  • 不保证持久性:数据在内存中,是否持久化由rdb/aof机制决定,与事务无关

2.2. 核心命令与执行流程

  • multi:开启事务。返回ok
  • 命令入队:后续的命令不会立即执行,而是返回 queued(放入客户端/服务器的事务队列)
  • exec:真正执行事务。按顺序执行队列中的命令
  • discard:放弃当前事务,清空事务队列

事务的错误处理

  • 入队错误(语法错误、命令不存在):在redis 2.6.5之后,如果入队阶段发生错误,exec 会直接返回错误,整个事务都会被丢弃
  • 运行时错误(如对string执行lpush,或incr一个非数字):命令在入队时不会报错,exec后,正确的命令会执行,错误的命令报错,且不支持回滚。这是redis事务与关系型数据库最大的区别

2.3. watch机制

在并发场景下,客户端可能先读取数据,再基于旧值构造事务进行修改。如果在提交事务前,该数据已被其他客户端修改,直接提交就可能造成数据不一致(如丢失更新)。redis 通过 watch 命令实现乐观锁(cas):watch 监视相关 key,若在 watch 之后、exec 之前这些 key 被修改过,则 exec 放弃执行整个事务并返回 nil,从而避免基于过期数据做出错误修改

2.3.1 watch 原理

  • redis 通过 watch 实现乐观锁。redis 建立映射:key -> 监视该 key 的客户端列表,客户端也记录自己监视了哪些 key。在 watch 之后、exec 之前,只要任何客户端(包括自己)修改了被监视的 key,redis 就会通过 touchwatchedkey 给监视该 key 的客户端打上 client_dirty_cas 标志。执行 exec 时,如果客户端带有这个标志,就放弃整个事务并返回 nil,事务中的命令都不执行;否则正常执行。exec 后自动取消所有 watch

2.3.2 实操演示

# 客户端1
watch k1          # 记录 k1 的版本号(假设是 0)
multi
set k1 100        # 入队,但不提交
set k2 1000       # 入队
# 客户端2(此时执行)
set k1 200        # 修改成功,k1 版本号变为 1
# 客户端1 再执行
exec              # 比对版本号:客户端是 0,服务器是 1。版本不一致!
(nil)             # 事务失败,所有命令都不执行

2.3.3 unwatch

取消对所有key的监控,相当于watch的逆操作
exec 和 discard 执行后,也会自动取消所有watch

watch 适合什么场景

watch 本质是cas(compare and swap)思路,在很多并发编程和数据库(如mysql的乐观锁)中都有应用。它适合读多写少的并发场景,如果在高并发写场景下,事务很容易因为版本冲突而不断重试失败,影响性能

3. 高频面试题 q&a 速查表

q1:rdb和aof的区别?如何选择

  • rdb:紧凑二进制,恢复快,适合冷备和主从复制。但实时性差,fork开销大
  • aof:文本协议,实时性好(默认每秒同步),可读性强。但文件体积大,恢复慢
  • 选择:如果对数据安全性要求极高,选aof(everysec);如果追求恢复速度,容忍少量数据丢失,选rdb;生产环境推荐混合持久化(redis 4.0+)

q2:aof重写时,主进程在做什么

  • 主进程继续响应其他客户端请求
  • 将新写入的命令追加到 aof_buf 以同步到旧aof文件
  • 将新写入的命令同时追加到 aof_rewrite_buf,等待子进程重写完成后,追加到新aof文件,保证数据不丢失

q3:redis事务支持原子性吗

  • 不支持严格的原子性。它只保证命令的“批量执行”和“连续执行不被加塞”
  • 如果发生运行时错误(如类型错误),redis不会回滚,其余命令会继续执行

q4:watch命令的实现原理

  • 基于版本号(cas乐观锁)。watch记录key的当前版本号,exec时比对,如果版本号发生变化(被其他客户端修改),则拒绝执行事务,返回nil

q5:为什么redis执行bgsave时,不阻塞主进程

  • 利用操作系统的 fork 和 写时复制(copy-on-write) 机制。fork出的子进程共享父进程内存,只有当父进程有写操作时,才会复制被修改的内存页,极大降低了内存开销和阻塞时间

到此这篇关于redis 持久化与事务:rdb、aof、multi/exec 与 watch 全解析的文章就介绍到这了,更多相关redis 持久化与事务内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

赞 (0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com