📌 核心原理:
Redisson 的分布式锁(主要实现类 RLock)主要基于 Redis Lua 脚本来确保操作的原子性,利用 Redis 的 SET命令配合 NX(不存在才设置)和 PX(过期时间)选项来实现锁的获取。其关键机制包括:
1、锁获取:
- 客户端尝试执行 Lua 脚本(本质是 SET lock_name random_value NX PX <leaseTime>)。
- NX确保只有一个客户端能成功设置这个键。
- PX为锁设置一个过期时间(租约时间 - lease time),即使持有锁的客户端崩溃,锁也能自动释放,避免死锁。
2、锁释放:
- 客户端执行另一个 Lua 脚本。
- 脚本会校验 Redis 中存储的锁值是否与客户端持有的 random_value(唯一 UUID)匹配。
- 只有值匹配时,才会执行 DEL命令删除键。
- 这是防误删的关键,确保只有锁的持有者才能释放自己的锁。
3 、 锁续期(Watchdog):
- Redisson 在客户端获取锁成功后,会启动一个后台守护线程(看门狗)。
- 这个线程会定期(默认 leaseTime / 3,初始租约默认 30s,则每 10s 续一次)检查持有锁的客户端是否依然存活。
- 如果客户端还在活动(持有 JVM 锁),它会自动延长锁的过期时间(续租),维持锁的有效性。
- 作用:防止业务逻辑执行时间超过最初设置的锁过期时间而导致锁意外失效的问题。除非持有锁的 JVM 挂掉,锁才会过期释放。
4、可重入性(Reentrancy):
- RLock实现了 Java Lock接口,支持可重入。
- 同一个 JVM 中同一个线程可以多次获取同一个锁(lock()方法),内部维护一个计数器。计数器归零时才真正释放锁。
🧠 常用 API 及方法:
Redisson 通过 RLock对象(获取:RedissonClient.getLock(String lockName)) 提供锁操作:
1、阻塞式获取锁:
如果获取锁失败会一直等待,直到获取成功,此时处理请求的线程就会"夯住",而应用的表象则是长时间未进行响应。
RLock lock = redissonClient.getLock("myLock");
// 阻塞直到获取成功。默认使用看门狗续期(leaseTime = -1)。
lock.lock();
// 指定锁的租约时间(leaseTime)。一旦超过该时间,锁自动释放,无论业务是否完成。
// 如果使用此方法指定 leaseTime,看门狗续期机制将失效!业务必须在 leaseTime 内完成。
lock.lock(10, TimeUnit.SECONDS);
try {
// 你的受保护的关键业务代码...
} finally {
// 必须在 finally 块中确保释放锁!
lock.unlock();
}
潜在问题:
- 线程资源占用:长时间阻塞会导致该线程一直处于等待状态,无法处理其他任务。在高并发场景下,如果有很多线程因等待同一个锁而阻塞,可能会耗尽系统的线程资源(线程池),导致应用性能下降甚至崩溃。
- 响应迟钝:对于用户请求或需要及时响应的服务来说,长时间阻塞会导致响应延迟或超时。
2、尝试获取锁(非阻塞或限时等待)
// 尝试获取一次,立刻返回获取结果(true/false)。
boolean acquired = lock.tryLock();
// 等待 waitTime 时间尝试获取锁,若在 waitTime 内仍未获取则返回 false。获取成功后,使用 leaseTime 作为锁过期时间(看门狗失效)。
// waitTime 内尝试获取锁时,并不是无脑的一直获取,为了减少 redis 的压力,采用了发布/订阅模式来获取其他线程释放锁的通知;也可能 waitTime 内未收到所释放通知,此时就会主动尝试获取锁
boolean acquired = lock.tryLock(long waitTime, long leaseTime, TimeUnit unit);
// 示例:尝试等待最多 5 秒获取锁,获取成功后锁持有时间最多 30 秒
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
try {
// 业务逻辑
} finally {
lock.unlock();
}
} else {
// 未能获取锁,处理其他逻辑(如重试、快速失败、降级等)
}
3、公平锁(RFairLock)
Redisson 也提供了公平锁的实现 (RFairLock)。获取方式与普通锁类似:
RLock fairLock = redissonClient.getFairLock("myFairLock");
fairLock.lock();
// ... 使用方式同 RLock ...
公平锁会严格按照客户端请求锁的顺序(进入队列的顺序)来授予锁,避免某些线程长时间饥饿。
4、联锁(MultiLock)
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RLock multiLock = redissonClient.getMultiLock(lock1, lock2, lock3);
multiLock.lock();
try {
// 需要对资源1、2、3进行原子性操作的业务逻辑...
} finally {
multiLock.unlock();
}
用于将多个独立的 RLock实例组合成一个逻辑锁。当且仅当能同时获取所有这些独立锁时,才算成功获取到整个联锁。释放时也一起释放。常用于控制多个资源的原子性操作。
5、RedLock
RedLock 是 Redis 的作者提出的一个多节点分布式锁算法,旨在解决使用单节点 Redis 分布式锁可能存在的单点故障问题。
6、读写锁
Redisson中支持分布式可重入读写锁,这种锁允许同时有多个读锁和一个写锁对同一个资源进行加锁。
RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();
📣 最佳实践与重要注意事项:
- 锁名称唯一且有意义:lockName应该对应其保护的特定共享资源(如 "order_create_lock:orderId_" + orderId)。
- 务必在 finally 块中释放锁:这是防止死锁的关键。
- 理解 lock.lock(leaseTime)与 lock.lock() :
- lock.lock()(或 lock.lock(-1, ...)): 强烈推荐!启用看门狗自动续期,适用于执行时间不确定的业务场景。
- lock.lock(timeout)/ tryLock(..., leaseTime, ...): 手动指定租约时间,看门狗失效。适用于执行时间非常确定且能确保在 leaseTime内完成的场景。
- 避免滥用 leaseTime 导致续期失效:除非有充分理由且能精确控制业务执行时间,否则优先使用默认的看门狗机制。
- 处理锁获取失败:使用 tryLock 时,必须妥善处理 false(获取失败)的情况,设计重试策略或降级逻辑。
- 谨慎使用锁的范围:分布式锁是重量级操作,尽量缩小锁保护的代码块(临界区)以提高系统吞吐量。
- 超时设置合理:tryLock中的 waitTime 和 leaseTime 要结合业务场景和系统压力合理设置。waitTime 过长可能导致线程积压;leaseTime 过短可能导致业务未完成锁已失效,过长则故障后资源不可用时间延长。
- Redis 高可用:分布式锁依赖于 Redis 的可靠性,确保使用 Redis 主从、集群或 Sentinel 等高可用方案,避免单点故障导致锁服务完全不可用。Redisson 适配了多种部署模式(单机/集群/哨兵)。
- 网络分区(脑裂)问题:Redis Cluster 在发生网络分区(脑裂)时可能导致多个客户端同时持有锁。这是分布式系统 CAP 理论权衡的一部分。如果对强一致性要求极高,需考虑其他方案(如 ZooKeeper、etcd 的有序性更强,但 Redis 性能更高)。
总而言之,Redisson 的分布式锁通过Lua 脚本保证原子性 、唯一 Value 防误删 、看门狗机制解决锁续期问题,使分布式锁的实现变得简单、健壮且具备生产级可靠性。