跳到内容

第 5 卷 / 第 14 章 / 第 2 课

14.2 Redis 命令模型、事务与持久化 ​


元数据卡

  • 前置:14.1 内存优先数据库
  • 关键词:Redis、原子命令、MULTI/EXEC、WATCH、RDB、AOF、复制
  • 代码语言:Redis CLI

Redis 的优势来自“围绕数据结构设计命令”,不是把关系表搬进内存。String、Hash、List、Set、Sorted Set、Stream 等类型各自提供原子操作;选错结构会同时伤害内存、延迟和可维护性。

优先用原子命令,不要读后覆盖 ​

命令示例适用于 Redis 7+ 的交互式 redis-cli,同一段须在同一连接执行。在空练习实例中操作这些固定 key;它们用于展示命令语义,不包含订单去重与库存不足检查。

text
INCR item:42:view_count
HINCRBY item:42 stock -1
ZINCRBY ranking 15 player:7

HINCRBY 的 item:42 是 Hash,后面的 item:42:stock 是另一个 String key,两种表示不可混用。对缺失库存直接减 1 会得到 -1;命令原子性不会自动补上“库存不可为负”这个业务条件。这些操作在命令边界上原子执行。若客户端先 GET、在本地计算、再 SET,多个客户端就可能覆盖彼此结果。

Redis 的命令执行从客户端视角大体是串行的,但现代 Redis 还使用后台线程、I/O 线程、子进程等完成其他工作。准确说法是“多数命令的核心数据结构操作在主执行线程上完成”,而不是“整个 Redis 只有一个线程”。慢命令、过大的 key 或 Lua 脚本仍可能拖住其他请求。

MULTI/EXEC 不是关系数据库事务 ​

text
SET item:42:stock 1
SET order:created 0
MULTI
DECRBY item:42:stock 1
INCR order:created
EXEC

MULTI 之后命令先排队,EXEC 时连续执行,不会与其他客户端命令交错。但它没有关系数据库式的自动回滚:某条命令在执行期因类型等原因失败,队列中其他命令仍可执行。

从本段初始化开始,EXEC 应返回 0、1,分别是剩余库存和已创建计数。要验证执行期失败不会回滚其他命令,在练习实例执行:

text
SET lab:typed text
SET lab:completed 0
MULTI
INCR lab:typed
INCR lab:completed
EXEC
GET lab:completed

INCR lab:typed 会报告值不是整数,但后一个 INCR 仍成功,GET lab:completed 得到 1。排队期的语法错误则会让 EXEC 拒绝整个队列,不能与执行期错误混为一谈。

需要条件更新时,用 WATCH 做乐观检查:

text
SET item:42:stock 1
WATCH item:42:stock
GET item:42:stock

MULTI
DECRBY item:42:stock 1
EXEC

在 GET 与 MULTI 之间,应用必须确认库存是有效整数且大于 0;否则 UNWATCH 并结束,不进入扣减。这里初始化为 1,因此无竞争时 EXEC 得到 0。要制造冲突,在 A 的 GET 之后由 B 执行 INCR item:42:stock,再回 A 执行 MULTI 到 EXEC:应得到 nil/null 事务结果,整队列不执行,库存仍为 2。空事务的空数组不是这种冲突结果。

若受监视 key 在 WATCH 后被改动,客户端必须重新读取、重新判断并有界重试。对多步校验更适合用服务端 Lua/Functions 把逻辑压缩成一次原子执行,但脚本也应短小并限制输入规模。Lua/Functions 的原子执行同样不提供运行时错误回滚,必须先完成可预见的参数和类型检查,再修改状态。

RDB:时间点快照 ​

RDB 周期性生成数据集的紧凑快照。通常由子进程写临时文件,再原子替换旧快照,父进程继续服务;写时复制让父子初始共享内存页。

它的主要取舍:

  • 文件紧凑,备份和大数据集重启通常较快;
  • 两次快照之间的写入可能在故障中丢失;
  • fork() 本身可能造成延迟,写密集期间 COW 会增加内存占用;
  • RDB 文件仍需复制到独立故障域,单机文件不是灾备。

不能机械地说峰值内存“一定翻倍”,实际取决于快照期间被改写的内存页比例、分配器和内核行为;容量规划应以压测峰值为准。

AOF:记录写命令并按策略刷盘 ​

AOF 记录修改数据集的命令,重启时重放。appendfsync 的常见策略:

策略确认路径典型数据窗口
always每批写入都请求 fsync 后确认最小,但仍受硬件和故障模型约束
everysec后台大致每秒 fsync通常按约一秒窗口规划,不是存储卡顿下的硬上界
no交给操作系统刷新窗口由操作系统策略决定

AOF 会增长,需要重写为能重建当前状态的更短表示。Redis 7 起采用 multi-part AOF:base 文件、一个或多个 incremental 文件及 manifest 共同描述有效日志集合;不能再把重写简化成“一个 AOF 文件被换掉”。

同时启用 AOF 与 RDB ​

两者可以同时启用;重启时 Redis 会优先用信息更完整的 AOF 恢复。AOF base 可以使用 RDB 格式,这与“同时保存独立 RDB 快照用于备份”不是同一个概念。

持久化配置应从业务容忍度倒推:

  • 纯可重建缓存可关闭持久化,但要验证缓存全失时数据库能否承受回源;
  • 可接受秒级窗口时可评估 AOF everysec;
  • 高价值权威数据还需要复制、备份、恢复演练和幂等写入,不能只靠 AOF;
  • WAIT 确认副本已处理此前写入,确认数和持久化不是同一件事;Redis 7.2+ 的 WAITAOF 提供针对本机/副本 AOF 刷盘的等待接口。两者都需检查实际返回的确认数。WAIT 能等待副本确认,却不会自动把 Redis 变成强一致 CP 数据库,故障切换仍可能受持久化与复制配置影响。

淘汰与持久化是两条轴 ​

maxmemory-policy 决定内存达到上限时淘汰哪些 key;RDB/AOF 决定如何恢复已存在的数据。一个作为权威存储的实例若允许淘汰业务数据,即使 AOF 每次刷盘,也可能把淘汰操作忠实持久化。

因此必须明确实例角色:

  • 缓存:允许淘汰,源数据可重建;
  • 会话或队列:需要定义丢失、重复和过期语义;
  • 权威状态:通常不允许任意淘汰,并需要比单节点持久化更完整的可靠性设计。

运维检查 ​

text
INFO persistence
INFO memory
SLOWLOG GET 20
LATENCY DOCTOR

关注最近 RDB/AOF 状态、重写是否卡住、fork/COW 内存、内存碎片、慢命令和主从延迟。恢复演练时要实际从备份启动新实例并校验 key 数、关键业务对象和应用读写,而不是只检查文件存在。

参考 ​

完成这一章后,应能把“内存快”拆成数据布局、执行模型、持久化确认和恢复预算四个可验证问题。

Built with VitePress | Software Systems Atlas