跳到主要内容

分布式锁

问题

为什么需要分布式锁?Redis、ZooKeeper、MySQL 各如何实现分布式锁?各方案的优缺点是什么?

答案

为什么需要分布式锁

单机环境中 synchronized / ReentrantLock 可以保证线程安全,但在分布式部署下多个 JVM 进程无法共享同一把锁。分布式锁通过外部存储(Redis、ZooKeeper、MySQL)来实现跨进程互斥。

分布式锁的核心要求

要求说明
互斥性同一时刻只有一个客户端持有锁
防死锁持锁客户端崩溃后,锁能自动释放(超时机制)
可重入同一客户端可重复加锁(选项)
高可用锁服务本身要高可用
安全释放只有持锁者能释放自己的锁

方案一:Redis 分布式锁

基础版:SETNX + 超时
// 加锁:SET key value NX EX seconds(原子操作)
String lockKey = "lock:order:" + orderId;
String requestId = UUID.randomUUID().toString();

Boolean acquired = redis.opsForValue()
.setIfAbsent(lockKey, requestId, 30, TimeUnit.SECONDS);

if (Boolean.TRUE.equals(acquired)) {
try {
// 执行业务逻辑
doBusiness();
} finally {
// 释放锁:Lua 脚本保证原子性(只释放自己的锁)
String script = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
""";
redis.execute(new DefaultRedisScript<>(script, Long.class),
List.of(lockKey), requestId);
}
}

Redisson(推荐生产使用)

Redisson 分布式锁
RLock lock = redissonClient.getLock("lock:order:" + orderId);
try {
// 尝试加锁,最多等待 10 秒,锁自动过期 30 秒
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
try {
doBusiness();
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

Redisson 内部通过 Watchdog 看门狗 自动续期(默认每 10 秒续期到 30 秒),解决了锁超时问题。详见 Redis 分布式锁

方案二:ZooKeeper 分布式锁

基于 ZooKeeper 的 临时顺序节点 实现:

Curator 框架实现
InterProcessMutex lock = new InterProcessMutex(curatorClient, "/locks/order");
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
try {
doBusiness();
} finally {
lock.release();
}
}
} catch (Exception e) {
log.error("获取锁失败", e);
}

优点

  • 临时节点天然防死锁(客户端断开连接,节点自动删除)
  • 监听机制避免轮询(惊群问题通过顺序节点解决)
  • 强一致性(ZAB 协议保证)

方案三:MySQL 分布式锁

基于唯一索引
-- 创建锁表
CREATE TABLE distributed_lock (
lock_key VARCHAR(64) PRIMARY KEY,
request_id VARCHAR(64) NOT NULL,
expire_time DATETIME NOT NULL
);

-- 加锁:插入成功则获取锁
INSERT INTO distributed_lock (lock_key, request_id, expire_time)
VALUES ('order_lock', 'uuid-xxx', NOW() + INTERVAL 30 SECOND);

-- 释放锁:删除自己的记录
DELETE FROM distributed_lock WHERE lock_key = 'order_lock' AND request_id = 'uuid-xxx';

一般不推荐用 MySQL 实现分布式锁(性能差、实现复杂),通常作为兜底方案。

三种方案对比

维度RedisZooKeeperMySQL
性能最高中等最低
一致性AP(主从可能丢锁)CP(强一致)强一致
可靠性中等(RedLock 提升)
实现复杂度低(Redisson)低(Curator)
自动续期WatchdogSession 超时自动释放需定时任务
适用场景高并发、允许极端情况失锁强一致性要求已有 MySQL 无额外组件

常见面试问题

Q1: Redis 和 ZooKeeper 分布式锁怎么选?

答案

  • Redis:性能高(10万+ QPS),适合高并发场景。缺点是 Redis 主从切换时可能丢锁(Master 宕机,锁未同步到 Slave)
  • ZooKeeper:强一致(CP),锁更可靠。缺点是性能较 Redis 低,适合对一致性要求高的场景

大多数互联网项目选 Redis(Redisson),金融等强一致性场景选 ZooKeeper(Curator)

Q2: Redis 分布式锁的主从问题怎么解决?

答案

问题:客户端在 Master 加锁成功,Master 宕机,锁还未同步到 Slave,Slave 晋升为新 Master 后另一个客户端也能加锁 → 两个客户端同时持锁。

解决方案:

  1. RedLock 算法:向 5 个独立 Redis 实例加锁,过半成功才算加锁成功。但有争议(Martin Kleppmann 指出时钟漂移问题)
  2. Redisson 的 RedLock 实现已被弃用
  3. 实际方案:接受极端情况下的不一致,业务层做兜底(如数据库乐观锁)

Q3: 如何防止锁过期但业务未完成?

答案

  1. Watchdog 自动续期:Redisson 默认开启看门狗,每 leaseTime / 3 自动续期
  2. 合理设置超时时间:根据业务处理时间设置足够长的锁超时
  3. 兜底机制:业务层加乐观锁(版本号),即使锁失效也能防止并发问题

Q4: 分布式锁是不是越用越好?

答案

不是。分布式锁引入了额外的依赖和延迟,应该尽量减少使用:

  1. 优先考虑 数据库唯一约束乐观锁 等无锁方案
  2. 分布式锁的粒度要细(按 orderId 加锁,不要全局锁)
  3. 持锁时间要短(只锁关键操作)
  4. 考虑是否真的需要互斥(有些场景幂等处理就够了)

相关链接