单机 synchronized 跨进程就失效了,得上分布式锁。三种常见做法:
一、Redis SETNX
SET lock:order:123 <uuid> NX PX 30000
NX 保证只有一个客户端能设上,PX 给过期时间防死锁,value 用 <uuid> 是为了只能解自己加的锁。
释放时必须用 Lua 保证原子性(先比对 uuid 再删):
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
坑:业务执行时间超过锁过期时间,锁被别人拿走,两个线程同时进入临界区。解法是「看门狗」自动续期(Redisson 的 watchdog),或者把过期时间设得足够大 + 业务侧幂等兜底。
二、数据库唯一索引 / 悲观锁
INSERT INTO locks (name, owner) VALUES ('order:123', 'uuid');
COMMIT;
依赖唯一约束,抢到插入成功即拿到锁。
坑:锁释放(删行)后连接异常没删干净会永久死锁,必须配合超时清理任务。性能也最差,高并发下别用。
三、ZooKeeper / etcd 临时顺序节点
客户端建临时节点,会话断开节点自动消失,天然防死锁;顺序节点实现公平锁。
坑:性能不如 Redis,且有「GC 停顿导致会话超时」的经典问题——JVM 长时间 STW,ZK 以为客户端挂了删了锁,等 GC 结束客户端以为自己还持有锁。etcd 的 lease 机制同理。
怎么选
| 方案 | 性能 | 可靠性 | 适用 |
|---|---|---|---|
| Redis | 高 | 中(有超期风险) | 大多数业务,允许偶发失效 |
| 数据库 | 低 | 中 | 已有 DB、并发低 |
| ZK / etcd | 中 | 高 | 金融、强一致场景 |
我的建议:绝大多数场景 Redis + 看门狗 + 业务幂等 就够了,别为了「绝对可靠」上 ZK,运维成本翻倍。
楼主 · 2026-09-28 13:21 · 浏览 3

