分布式锁
2026/1/15大约 3 分钟
分布式锁
为什么需要分布式锁
单机环境下,可以使用 synchronized 或 ReentrantLock。
分布式环境下,多个进程需要协调访问共享资源,需要分布式锁。
分布式锁的要求
- 互斥性:同一时刻只有一个客户端持有锁
- 防死锁:客户端崩溃后锁能自动释放
- 可重入:同一客户端可以多次获取锁
- 高可用:锁服务高可用
- 高性能:加锁解锁性能好
Redis 分布式锁
基本实现
// 加锁
Boolean result = redisTemplate.opsForValue()
.setIfAbsent("lock:order", "value", 30, TimeUnit.SECONDS);
// 解锁
redisTemplate.delete("lock:order");问题1:锁被误删
// 问题:A 加锁后超时,锁自动释放
// B 获取锁,A 执行完删除了 B 的锁
// 解决:加锁时设置唯一标识,解锁时验证
String value = UUID.randomUUID().toString();
redisTemplate.opsForValue().setIfAbsent("lock", value, 30, TimeUnit.SECONDS);
// 解锁时验证
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList("lock"), value);问题2:锁续期
// 问题:业务执行时间超过锁过期时间
// 解决:使用 Redisson,自动续期(看门狗机制)
RLock lock = redissonClient.getLock("lock:order");
try {
lock.lock(); // 默认30秒,自动续期
// 业务逻辑
} finally {
lock.unlock();
}Redisson 分布式锁
// 可重入锁
RLock lock = redissonClient.getLock("lock");
lock.lock();
lock.unlock();
// 公平锁
RLock fairLock = redissonClient.getFairLock("fairLock");
// 读写锁
RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
rwLock.readLock().lock();
rwLock.writeLock().lock();
// 红锁(多节点)
RLock lock1 = redisson1.getLock("lock");
RLock lock2 = redisson2.getLock("lock");
RLock lock3 = redisson3.getLock("lock");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock();RedLock 算法
- 获取当前时间
- 依次向 N 个 Redis 节点请求加锁
- 如果在大多数节点(N/2+1)加锁成功,且总耗时小于锁过期时间,则加锁成功
- 加锁失败则向所有节点释放锁
Zookeeper 分布式锁
实现原理
利用 Zookeeper 的临时顺序节点。
/locks
├── lock-0000000001 (客户端A)
├── lock-0000000002 (客户端B)
└── lock-0000000003 (客户端C)- 创建临时顺序节点
- 获取所有子节点,判断自己是否最小
- 如果是最小,获取锁成功
- 如果不是,监听前一个节点的删除事件
- 前一个节点删除后,重新判断
Curator 实现
// 可重入锁
InterProcessMutex lock = new InterProcessMutex(client, "/locks/order");
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
// 业务逻辑
}
} finally {
lock.release();
}
// 读写锁
InterProcessReadWriteLock rwLock = new InterProcessReadWriteLock(client, "/locks/rw");
rwLock.readLock().acquire();
rwLock.writeLock().acquire();
// 信号量
InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, "/locks/semaphore", 10);
Lease lease = semaphore.acquire();
semaphore.returnLease(lease);Redis vs Zookeeper
| 特性 | Redis | Zookeeper |
|---|---|---|
| 实现方式 | SETNX | 临时顺序节点 |
| 一致性 | AP | CP |
| 性能 | 高 | 较低 |
| 可靠性 | 较低 | 高 |
| 复杂度 | 简单 | 复杂 |
选择建议
- Redis:性能要求高,可以容忍极端情况下的不一致
- Zookeeper:可靠性要求高,对性能要求不高
数据库分布式锁
基于唯一索引
-- 加锁
INSERT INTO distributed_lock (lock_name, owner, expire_time)
VALUES ('order_lock', 'client1', NOW() + INTERVAL 30 SECOND);
-- 解锁
DELETE FROM distributed_lock WHERE lock_name = 'order_lock' AND owner = 'client1';基于悲观锁
-- 加锁
SELECT * FROM distributed_lock WHERE lock_name = 'order_lock' FOR UPDATE;
-- 解锁
COMMIT;缺点
- 性能差
- 单点故障
- 无法自动续期
分布式锁最佳实践
1. 设置合理的过期时间
// 根据业务执行时间设置,留有余量
lock.lock(30, TimeUnit.SECONDS);2. 使用 try-finally 确保释放
try {
lock.lock();
// 业务逻辑
} finally {
lock.unlock();
}3. 避免长时间持有锁
// 只在必要的代码块加锁
lock.lock();
try {
// 只包含需要同步的代码
} finally {
lock.unlock();
}
// 其他代码不需要锁4. 考虑锁的粒度
// 粗粒度:锁整个订单
lock("order");
// 细粒度:锁单个订单
lock("order:" + orderId);