不能用setnx+expire因非原子操作:若setnx成功后崩溃,锁将永不过期;必须用set ex/px nx原子命令,并用lua脚本校验value后删除锁,防止误删;强一致场景需redlock多节点容错。

为什么不能用 SETNX + EXPIRE 两步设锁
因为不是原子操作:先 SETNX 成功,但还没来得及 EXPIRE 就崩溃或网络中断,锁就永远不释放了——这就是典型的死锁源头。Redis 2.6.12+ 的 SET 命令支持 NX 和 PX 同时传入,必须用这个组合,否则锁不可靠。
redis.set(..., 'NX', 'PX', ttl) 是基础,但释放锁必须用 Lua 脚本
释放锁时只做 DEL 是危险的:A 拿到锁,超时前没处理完;B 等超时后抢到新锁;此时 A 执行 DEL 会误删 B 的锁。所以必须用 Lua 脚本比对 value(即唯一 token)再删:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
注意:ARGV[1] 必须是加锁时写入的随机字符串(比如 crypto.randomBytes(16).toString('hex')),不能是固定值或时间戳。
用 redlock 还是手写单节点锁?看你的容错要求
单 Redis 实例(哪怕主从)挂了,整个锁服务就瘫痪。如果你的业务能接受短暂锁失效(比如库存扣减允许少量超卖),手写单节点锁够用;但涉及资金、订单幂等、任务去重等强一致性场景,必须上 redlock。
-
redlock要求至少 3 个独立 Redis 实例(非集群模式),获取锁时需在 ≥ N/2+1 个节点成功才算拿到锁 - 驱动要用
ioredis(支持管道和重连),redis官方库 v4+ 对eval的参数写法变了,传参必须用{ keys: [...], arguments: [...] }形式 - 锁超时时间(
ttl)要明显大于业务执行最大耗时,否则锁自动释放后其他客户端可能并发进入临界区
最容易被忽略的三个细节
一是锁 key 的命名必须带业务上下文,比如 lock:order:12345,而不是笼统的 lock,否则不同资源互相阻塞;二是所有 await 操作都得包在 try...finally 里,确保无论成功失败都尝试释放锁;三是测试时别只跑 3 次并发,真实压测要模拟网络延迟、Redis 暂时不可达、进程突然退出等异常路径——很多锁逻辑只在这些边界下才暴露问题。











