Redis SET 命令的 NX 和 EX 选项合并操作可原子实现加锁与设过期时间,避免 SETNX 后 EXPIRE 失败导致死锁;ZooKeeper 临时顺序节点依赖 ZAB 协议强一致性和 Session 自动清理,规避假死问题;数据库乐观锁本质是并发控制,非真正分布式锁,适用场景有限。
SETNX?单独调用 SETNX 只能保证“存在性判断+设值”原子性,但无法同时设置过期时间。如果先 SETNX 成功、再 EXPIRE 失败(如网络中断或进程崩溃),就会留下永不过期的死锁锁。
正确做法是用 SET 命令的 NX 和 EX 选项合并操作:
SET lock:order:123 "8a9b" NX EX 30
其中 "8a9b" 是客户端唯一标识(用于后续校验和释放),30 是过期秒数。这个命令在 Redis 2.6.12+ 支持,天然原子。
常见踩坑点:
EX 不被识别,返回错误或静默失败DEL 无校验)RLock 是怎么避免误删锁的?RLock 在加锁时自动生成唯一 threadId + UUID 作为锁 value,并用 Lua 脚本封装“判断 value 相等再 DEL”逻辑,确保只有加锁者能解锁。
它还支持自动续期(watchdog):默认 30 秒锁过期,但只要客户端还活着,每 10 秒会自动延长锁有效期。这解决了“业务耗时 > 锁 TTL”的经典问题。
但要注意:
RLock.unlock() 无法识别 value 格式,会直接报错或误删ZooKeeper 依靠 ZAB 协议保证写入强一致,锁释放不依赖超时,而是靠 Session 断连后自动删除临时节点,彻底规避“假死导致锁残留”问题。
典型流程是:客户端创建临时顺序节点(如 /lock/worker-000000001),再检查自己是否为最小序号节点;不是则监听前一个节点的删除事件。
优势与代价并存:
/lock 节点会互相干扰基于数据库的 UPDATE ... WHERE version = ? 是典型的乐观锁,但它本质不是“分布式锁”,而是“并发更新控制”。它不阻塞请求,只拒绝冲突写入。
真正用作分布式锁时(如插入唯一键记录),性能和可靠性都受限:
SQLIntegrityConstraintViolationException)才能感知锁失败,链路不直观INSERT ... ON DUPLICATE KEY UPDATE 或 PostgreSQL 的 INSERT ... ON CONFLICT DO NOTHING 可用,但吞吐量远低于 Redis真正需要分布式锁时,优先选 Redis(简单高效)或 ZooKeeper(强一致刚需);用数据库,往往是已有系统改造成本低的权衡,不是技术首选。