Redis SET 命令的 NX 和 EX 选项合并操作可原子实现加锁与设过期时间,避免 SETNX 后 EXPIRE 失败导致死锁;ZooKeeper 临时顺序节点依赖 ZAB 协议强一致性和 Session 自动清理,规避假死问题;数据库乐观锁本质是并发控制,非真正分布式锁,适用场景有限。

Redis SETNX + Lua 脚本为什么不能直接用 SETNX?
单独调用 SETNX 只能保证“存在性判断+设值”原子性,但无法同时设置过期时间。如果先 SETNX 成功、再 EXPIRE 失败(如网络中断或进程崩溃),就会留下永不过期的死锁锁。
正确做法是用 SET 命令的 NX 和 EX 选项合并操作:
SET lock:order:123 "8a9b" NX EX 30
其中 "8a9b" 是客户端唯一标识(用于后续校验和释放),30 是过期秒数。这个命令在 Redis 2.6.12+ 支持,天然原子。
常见踩坑点:
立即学习“Java免费学习笔记(深入)”;
- 误用老版本 Redis(NX 和
EX不被识别,返回错误或静默失败 - 没传客户端唯一值,导致任意客户端都能删锁(
DEL无校验) - 过期时间设太短,业务未执行完锁就自动释放,引发并发冲突
Redisson 的 RLock 是怎么避免误删锁的?
RLock 在加锁时自动生成唯一 threadId + UUID 作为锁 value,并用 Lua 脚本封装“判断 value 相等再 DEL”逻辑,确保只有加锁者能解锁。
它还支持自动续期(watchdog):默认 30 秒锁过期,但只要客户端还活着,每 10 秒会自动延长锁有效期。这解决了“业务耗时 > 锁 TTL”的经典问题。
但要注意:
- watchdog 依赖 Redisson 客户端心跳,若 JVM Full GC 时间过长(> 心跳间隔),可能导致续期失败而提前释放锁
- 非 Redisson 客户端(如 Jedis)加的锁,
RLock.unlock()无法识别 value 格式,会直接报错或误删 - RedLock 模式(多 Redis 实例)在多数场景下收益远低于复杂度,官方已不推荐,除非你有跨机房容灾强需求
ZooKeeper 的临时顺序节点方案为何适合强一致性场景?
ZooKeeper 依靠 ZAB 协议保证写入强一致,锁释放不依赖超时,而是靠 Session 断连后自动删除临时节点,彻底规避“假死导致锁残留”问题。
典型流程是:客户端创建临时顺序节点(如 /lock/worker-000000001),再检查自己是否为最小序号节点;不是则监听前一个节点的删除事件。
优势与代价并存:
- 没有过期时间概念,锁不会因网络抖动意外失效
- ZK 集群性能瓶颈明显,高并发抢锁易成瓶颈(尤其 watch 通知风暴)
- 客户端需处理连接丢失、session 过期、重复 watch 等大量状态逻辑,比 Redis 方案重得多
- 节点路径必须带业务前缀隔离,否则不同服务共用同一
/lock节点会互相干扰
数据库乐观锁在分布式环境下的适用边界在哪?
基于数据库的 UPDATE ... WHERE version = ? 是典型的乐观锁,但它本质不是“分布式锁”,而是“并发更新控制”。它不阻塞请求,只拒绝冲突写入。
真正用作分布式锁时(如插入唯一键记录),性能和可靠性都受限:
- 唯一索引冲突抛异常(
SQLIntegrityConstraintViolationException)才能感知锁失败,链路不直观 - MySQL 的
INSERT ... ON DUPLICATE KEY UPDATE或 PostgreSQL 的INSERT ... ON CONFLICT DO NOTHING可用,但吞吐量远低于 Redis - DB 连接池打满、主从延迟、事务超时都会放大锁失败率,不适合低延迟敏感型业务
- 无法实现可重入、等待队列、公平性等高级语义,仅适合简单“有/无”抢占场景
真正需要分布式锁时,优先选 Redis(简单高效)或 ZooKeeper(强一致刚需);用数据库,往往是已有系统改造成本低的权衡,不是技术首选。










