分布式锁方案对比与选型(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;只有数据库且低频 → 数据库乐观锁/唯一约束。不要为了“分布式锁”引入不必要的一致性负担。

小结

没有“最好”的分布式锁,只有匹配业务的一致性要求与性能预算。先回答三个问题:能容忍锁偶发失效吗?持锁时长是毫秒级还是秒级?团队运维能力支持哪套组件?答案定了,方案也就定了。

笔记加载中…