Redis 8.4 单 key 乐观锁:用 SET IFEQ/IFDEQ 替代 CAS Lua
Redis 8.4 于 2025-11 GA,2026-03 上 Redis Cloud。这个版本把单 key 的 compare-and-set 做进了 SET,并新增 DIGEST、DELEX、MSETEX。对低频编辑表单、配置项这类「读-改-写」场景,不必再为 CAS 单独写 Lua 脚本。
SET 的四个条件选项
SET key value IFEQ old-value
SET key value IFNE old-value
SET key value IFDEQ digest
SET key value IFDNE digest
IFEQ / IFNE 比较原值,IFDEQ / IFDNE 比较摘要。条件成立才写入,不成立不写,整个判断和写入是单 key 原子操作。
redis-cli 里:
127.0.0.1:6379> SET cfg:timeout 30
OK
127.0.0.1:6379> SET cfg:timeout 60 IFEQ 30
OK
127.0.0.1:6379> SET cfg:timeout 90 IFEQ 30
(nil)
第二次失败是因为当前值已经是 60。这里返回 nil 表示条件不满足,不是命令错误。
IFDEQ 与 DIGEST:客户端不必保存旧值
DIGEST key 返回值的摘要,IFDEQ 用 XXH3 哈希比较。客户端只需保存 digest,不必把完整旧值放进表单隐藏字段或会话里。value 很大时,这个差别很明显。
127.0.0.1:6379> SET doc:1 "long json ..."
OK
127.0.0.1:6379> DIGEST doc:1
"a1b2..."
127.0.0.1:6379> SET doc:1 "new json ..." IFDEQ a1b2...
OK
DIGEST 是独立读命令,读取 digest 与提交写入之间仍有时间窗;冲突时 IFDEQ 失败即可重试。它只是把比较依据从完整值换成摘要,本身不是锁。
DELEX:条件删除
DELEX key IFEQ val
DELEX key IFDEQ digest
原子 compare-and-delete。可用于删除缓存前确认内容没被改过,或按旧值清理任务条目。返回值要区分「删除成功」和「条件不匹配」,迁移时同样不能把 0 当异常。
MSETEX:多 key 统一过期
MSETEX numkeys key value ... [NX|XX] [EX|PX|EXAT|PXAT|KEEPTTL]
原子设置多个 key 并统一过期时间,替代 MSET / MSETNX 的常见用法。它解决的是批量写与 TTL 一致,不是多 key 的条件写入;跨 key 的 compare-and-set 仍需事务或 Lua。
适用与不适用
适用:低频编辑表单、配置项、单 key 文档。读一次拿值和 digest,提交时带 IFEQ / IFDEQ,冲突就提示用户合并。请求稀疏,重试成本低。
不适用:高冲突热点 key。大量并发同时 IFEQ 同一个旧值,只有一个成功,其余全部失败重试,吞吐不如排队或分布式锁。跨 key 事务也不适用:SET 条件只作用于目标 key,MSETEX 没有条件比较,多 key 一致性仍要回到 Lua 或事务。
从 Lua 迁移时容易漏的返回值判断
Lua 里常见写法:
local ok = redis.call('SET', KEYS[1], ARGV[1])
if not ok then
return 0
end
条件不满足时 redis.call 返回 false,脚本里用 if not ok 就能拦住。改成客户端直接发 SET ... IFEQ 后,返回值是 nil / null / None(RESP2 空批量),很多客户端不会抛异常。如果代码只捕获异常,就会把冲突当成成功。
伪代码:
def save(key, new_value, expected_digest):
r = redis.execute_command("SET", key, new_value, "IFDEQ", expected_digest)
if r is None:
return {"ok": False, "reason": "conflict"}
return {"ok": True}
同理,DELEX 返回整数,条件不满足是 0;DIGEST 对不存在的 key 返回 nil。这些分支都要显式判断,不能只看「命令没报错」。
另外,IFEQ 比较的是字符串原值,序列化格式(JSON 空格、数字类型)必须前后一致;IFDEQ 保存的是 DIGEST 命令返回的摘要,不是客户端本地计算的哈希,不要混用。