Redis 原子性、事务与 Lua
解释 Redis 单命令原子性与多步竞态的边界,以及 MULTI/EXEC、WATCH、Lua/Functions 在 read-check-write 和多 Key 状态转换中的职责。
#concept
#growing
#backend
#redis
[!info] related notes
- 所属 MOC:BodySense Redis 从零到一课程 MOC、Backend 常用中间件与基础设施 MOC
- 前置概念:Redis
- 相关概念:Refresh Token
- 对应课程:BodySense Redis 第 4 课
Redis 原子性、事务与 Lua
一句话定义
Redis 的单条命令通常具有原子执行语义,但应用层的多条命令组合并不会自动原子;需要根据状态转换选择更强的单命令、MULTI/EXEC、WATCH 或 Lua / Redis Functions。
危险形状:Read → Check → Write
GET state
→ application decides
→ SET new-state
多个客户端可以交错:
A GET
B GET
A SET
B SET
每条命令都原子,整体仍会发生 lost update 或多赢家竞态。
优先级
1. 优先使用已有原子命令
counter increment → INCR
create only if absent → SET ... NX
set membership → SADD / SREM
不要为了“原子性”把本来已经原子的单命令套进 Lua。
2. MULTI / EXEC
提供一组命令连续执行、不被其他普通客户端命令插入的执行边界。
[!warning] MULTI/EXEC 不等于关系数据库事务;它不是任意执行期错误都自动回滚前面已执行操作的 ACID transaction。
3. WATCH
适合乐观并发控制:
WATCH key
→ read
→ application check
→ MULTI / EXEC
如果 WATCH 之后 Key 被别人修改,事务可失败并由客户端重试。
4. Lua / Redis Functions
适合:
多个 Redis 操作
+ Redis 内部条件判断
+ 必须形成 single-winner 状态转换
整个脚本在 Redis 中原子执行,同时减少多次网络往返。
BodySense 典型场景
Rate Limit
INCR
+
首次请求 EXPIRE
如果分成两次网络操作,中间失败可能留下无 TTL counter;Lua 可把它收敛成一个原子状态转换。
Session 创建 / 撤销
SET session:<sid>
SADD user_sessions:<uid> <sid>
以及对应删除,需要避免应用崩溃造成 authority 与 secondary index 的主动写入不一致。
Refresh Token Rotation
check old token active
→ consume old token
→ write replay tombstone
→ create new token
→ update family
必须保持 single-winner,避免一个旧 Refresh Token 并发分叉出多个合法后代。
Lua 的边界
- 脚本应短、小、确定;
- 大循环或重计算会阻塞 Redis 命令处理路径;
- Lua 只能保护脚本覆盖的主动状态转换;
- TTL 自动过期不会自动触发应用自定义 Lua 清理关联索引;
- 跨 PostgreSQL + Redis 的原子性不能由一段 Redis Lua 自动解决。
速查
单条原子操作够用 → 原生命令
连续执行一组命令 → MULTI / EXEC
乐观 read-check-write → WATCH + MULTI / EXEC
Redis 内部条件 + 多步状态机 → Lua / Redis Functions