☰
Redis 之 【持久化 与 事务】
2026/9/30 2:40:27 网站建设 项目流程

目录

1. Redis持久化机制(RDB与AOF)

1.1. RDB(Redis DataBase):内存快照

1.1.1 触发机制

1.1.2 bgsave 运作流程

1.1.3 优缺点

1.1.4 RDB文件的处理

1.2. AOF(Append Only File):日志追加

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

1.2.2 文件同步策略(appendfsync)

1.2.3 AOF重写机制

1.2.4 启动时数据恢复

Redis 4.0 的混合持久化

2. Redis事务与乐观锁

2.1. Redis事务的ACID特性

2.2. 核心命令与执行流程

2.3. WATCH机制:乐观锁(CAS)的实现

2.3.1 WATCH 原理(版本号机制)

2.3.2 实操演示

2.3.3 UNWATCH

3. 高频面试题 Q&A 速查表


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 文件的内容,进行数据恢复

当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出的子进程共享父进程内存,只有当父进程有写操作时,才会复制被修改的内存页,极大降低了内存开销和阻塞时间

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询