编程 Redis 8.4 给 SET 加了 IFEQ/IFDEQ:条件写可以替代手写 Lua 乐观锁

2026-10-19 21:01:23

Redis 8.4 给 SET 加了 IFEQ/IFDEQ:条件写可以替代手写 Lua 乐观锁

Redis 8.4 给 SET 增加了四个条件选项:IFEQ / IFNE / IFDEQ / IFDNE,另外新增 DELEX、DIGEST、MSETEX。以前做 compare-and-set 要在 EVAL 里先 GET 再 SET;现在大部分场景一条命令就够。

文档:8.4 新特性、8.4 新增命令参考、发布博客。

以前的写法:EVAL 里 GET 再 SET

-- cas.lua:仅当当前值等于 ARGV[1] 时写入 ARGV[2]
local cur = redis.call('GET', KEYS[1])
if cur == ARGV[1] then
  redis.call('SET', KEYS[1], ARGV[2])
  return 1
end
return 0

脚本靠 Redis 原子执行脚本来保证安全,代价是每个写路径都要带一份 Lua、维护 KEYS/ARGV 约定,并且每次比较都得把完整旧值传到服务端。

SET 的条件写

SET config:app '{"retries":3}' IFEQ '{"retries":2}'   # 当前值等于给定旧值才写 → OK
SET config:app '{"retries":3}' IFEQ '{"retries":9}'   # 不匹配 → 不写入
SET lock:job:7 worker-b IFNE worker-a                 # 当前值不等于给定值才写
SET config:app '{"retries":3}' IFDNE b1e4...          # 摘要不等才写

IFEQ/IFNE 比较的是值本身,IFDEQ/IFDNE 比较的是 DIGEST 摘要。这套语义可以直接做出无锁数据结构和乐观并发控制,不需要额外锁键。

返回值:条件不满足不是错误

写入成功返回 OK。条件不满足时命令不写入,返回 nil(RESP 空回复),不是报错。客户端通常把它映射成 null/nil,所以判断逻辑要写成「拿到 OK 才算写成功」,不要假设“没抛异常就写进去了”。不同客户端对空回复的表示不完全一致,接入时对着命令参考核一遍返回类型。

DIGEST:不用再把 1MB 的旧值传上来

DIGEST key 返回值内容的十六进制摘要。旧值很大时(比如 1MB 的 JSON blob),条件写的成本主要是把旧值完整传到服务端再逐字节比较;换成摘要,比较参数只有几十字节。

DIGEST config:app
# "b1e4..."                                     先取摘要(这一次读仍要传完整值)
SET config:app '{"retries":3}' IFDEQ b1e4...    # 摘要相等才写
SET config:app '{"retries":3}' IFDNE b1e4...    # 摘要不等才写

摘要省的是比较阶段的网络开销,不省“怎么知道旧摘要”——摘要本身仍要先读到客户端并保存好。

DELEX:按值删除

DELEX 只在值满足条件时删除键,条件和 SET 那套一致(值相等或摘要相等),语法细节以命令参考为准。适合“处理完就删”的键:谁先删成功谁拿到处理权,不用先抢一把锁再 DEL。

DELEX queue:item:88 IFEQ 'pending'
DELEX queue:item:88 IFDEQ b1e4...

MSETEX:多键写入加过期,一次原子完成

MSETEX session:a v1 session:b v2 EX 3600      # 写入并统一设过期
MSETEX session:a v1 session:b v2 EX 3600 XX   # 只有这些键都已存在才执行
MSETEX session:a v1 session:b v2 EX 3600 NX   # 只有这些键都不存在才执行

NX/XX 判断的是“全部键”,不是逐个键决定,属于全有或全无:只要有一个键不满足,整条命令都不写。适合批量刷新一组缓存 TTL,或一次性初始化一组键。

边界

  • 值比较是按字节相等。JSON 字段顺序、空格、数字格式(1 与 1.0)不同都算不等;参与比较的序列化必须固定。
  • 大 value 的比较发生在服务端,单次命令耗时随 value 增大而增加。热路径上的大对象优先用 IFDEQ/IFDNE。
  • 摘要不能当版本号复用:值一变摘要就变,客户端保存的旧摘要立即失效,重试逻辑按“不匹配就重读”写。
  • MSETEX 一次操作多个键,集群模式下这些键必须落在同一 slot(可用 hashtag),否则命令无法执行。
  • 8.4 的 CLUSTER MIGRATION(原子槽迁移)进行期间,FT.SEARCH / FT.AGGREGATE / FT.HYBRID 和 TS.* 可能返回部分结果或重复数据,迁移窗口内的搜索类读取要能接受这一点。

什么时候仍然应该用 Lua 或 WATCH

条件写只覆盖“值相等 / 摘要相等”这一类判断。下面几种还得回到 EVAL 或 WATCH/MULTI:

  • 条件不是相等:比较数字大小、按 JSON 字段判断(得先解出内容)、多个键联动判断。
  • 需要“读—算—写”的复合逻辑,一条命令表达不了。
  • 要在同一事务里做多次读写,WATCH key + MULTI/EXEC 更直观。
  • 客户端或线上版本还在 8.4 之前。

8.4 其它几处变化

  • CLUSTER MIGRATION:原子槽迁移。
  • XREADGROUP CLAIM min-idle-time:一条命令同时消费空闲的 pending 条目和新到达条目。
  • FT.HYBRID:支持 RRF 与 LINEAR 两种混合排序。
  • aof-load-corrupt-tail-max-size:启动时自动修复 AOF 尾部损坏。
复制全文 生成海报 Redis 乐观锁 CAS Lua 缓存

推荐文章

程序员茄子在线接单