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 尾部损坏。