分布式锁方案对比与选型(Redis/ZooKeeper/DB)
单机多线程用 synchronized/Lock,多实例部署后临界区散落在不同进程,就需要分布式锁:让多个进程互斥地访问共享资源。主流方案是 Redis、ZooKeeper 和数据库三种,各有取舍。
分布式锁的三要素
- 互斥:同一时刻只有一个进程持有锁。
- 防死锁:持锁方崩溃后锁能自动释放(过期时间/临时节点)。
- 可重入/续期:同一线程可重复加锁;长任务能续期。
方案一:Redis(SETNX + 过期时间)
// 原子加锁:SET key value NX PX 过期时间
Boolean ok = stringRedisTemplate.opsForValue()
.setIfAbsent("lock:pay:" + orderId, token, Duration.ofSeconds(30));
if (Boolean.TRUE.equals(ok)) {
try {
// 临界区业务
} finally {
// 释放前校验 token(只有自己才能释放),用 Lua 保证原子
String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) else return 0 end";
stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
List.of("lock:pay:" + orderId), token);
}
}
- 优点:性能最好,实现简单,适合高并发场景。
- 缺点:Redis 主从切换可能丢锁(主没同步就宕机,从节点没有该 key),极端并发下可能两个进程同时持锁。
- 生产实践:直接用 Redisson 的
RLock——原生支持看门狗自动续期与原子释放,避免自己实现过期续期:
RLock lock = redissonClient.getLock("lock:pay:" + orderId);
boolean got = lock.tryLock(3, 30, TimeUnit.SECONDS); // 等待 3s,持锁 30s
if (got) {
try { /* 临界区 */ } finally { if (lock.isHeldByCurrentThread()) lock.unlock(); }
}
关于分布式环境下的 Redlock 算法与极端一致性要求,业界有争议,是否需要取决于业务对“锁失效窗口”的容忍度。
方案二:ZooKeeper(临时顺序节点)
原理:在锁路径下创建临时顺序节点,只允许序号最小的节点持锁,其余节点监听前一个节点删除事件,形成等待队列。临时节点随会话断开自动删除,天然防死锁。
// 使用 Curator 客户端
InterProcessMutex lock = new InterProcessMutex(client, "/locks/order-" + orderId);
if (lock.acquire(3, TimeUnit.SECONDS)) {
try { /* 临界区 */ } finally { lock.release(); }
}
- 优点:强一致(ZAB 协议),无“主从切换丢锁”问题,天然公平。
- 缺点:吞吐低于 Redis,会话过期抖动可能导致锁提前释放;引入额外组件 ZooKeeper。
方案三:数据库(悲观锁/乐观锁/唯一约束)
-- 悲观锁:事务内锁定行,其他事务阻塞等待
SELECT * FROM t_order WHERE id = 100 FOR UPDATE;
-- 乐观锁:版本号比对,适合“读多写少”
UPDATE t_order SET status=20, version=version+1 WHERE id=100 AND version=1;
- 优点:零额外组件,业务数据就在库内,事务边界天然一致。
- 缺点:性能最差,锁范围难控,长时间持锁拖垮数据库;适合低频后台任务与对账类场景。
横向对比与选型
| 维度 | Redis(Redisson) | ZooKeeper(Curator) | 数据库 |
|---|---|---|---|
| 一致性 | AP,弱一致风险 | CP,强一致 | 强一致(事务) |
| 性能 | 高 | 中 | 低 |
| 防死锁 | 过期时间+看门狗 | 临时节点 | 事务超时 |
| 额外组件 | 已有 Redis 即可 | 需引入 ZK | 无 |
| 典型场景 | 秒杀、扣库存等高频 | 分布式调度、元数据互斥 | 低频对账、简单任务 |
选型建议:已有 Redis 且能容忍极小概率锁失效 → Redisson;要求强一致且低频 → ZooKeeper;只有数据库且低频 → 数据库乐观锁/唯一约束。不要为了“分布式锁”引入不必要的一致性负担。
小结
没有“最好”的分布式锁,只有匹配业务的一致性要求与性能预算。先回答三个问题:能容忍锁偶发失效吗?持锁时长是毫秒级还是秒级?团队运维能力支持哪套组件?答案定了,方案也就定了。