介绍
官方定义:Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。
简单来讲Redisson是一个在Redis的基础上实现的分布式工具的集合。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
Redisson实现分布式锁
- 引入redisson依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.45.1</version>
</dependency>
温馨提示:此外还有一种引入方式,直接引入redission的starter依赖,然后在yam|文件中配置Redisson,但是不推荐这种方式,因为他会替换掉Spring官方提供的这套对 Redisson 的配置。所以我们采用@Bean手动配置。
- RedissonConfig:配置Redisson客户端
/**
* Redisson配置类
*/
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
// 配置类
Config config = new Config();
// 添加redis地址,这里添加了单点的地址,也可以使用config.useClusterServers()添加集群地址
config.useSingleServer().setAddress("redis://192.168.8.100:6379").setPassword("123321");
// 创建RedissonClient客户端对象
return Redisson.create(config);
}
}
以下是 Redisson 中 lock()
和 tryLock()
方法所有重载形式的详细解析,重点说明其是否支持看门狗机制,并对比不同方法的适用场景。
一、lock()
方法详解
1. 无参形式:void lock()
- 行为:无限阻塞直到获取锁,线程会一直等待,直到锁可用或线程被中断。
- 看门狗机制:✅ 支持
默认启用看门狗,锁的初始租期为 30 秒,每隔 10 秒(租期的 1/3)自动续期一次,确保业务未完成时锁不会过期。 - 适用场景:
- 需要严格保证互斥性的场景(如金融交易、库存扣减)。
- 无法预估业务耗时,需依赖自动续期避免锁过期。
public void criticalTask() { RLock lock = redisson.getLock("criticalLock"); lock.lock(); try { // 执行耗时不确定的核心业务 processUnpredictableTask(); } finally { if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); } } }
2. 带租期参数:void lock(long leaseTime, TimeUnit unit)
行为:获取锁后,锁在
leaseTime
时间后自动释放,无论业务是否完成。看门狗机制:❌ 不支持
显式指定leaseTime
会禁用看门狗,锁到期后强制释放,可能导致业务未完成时锁失效。适用场景:
- 明确知道业务最大耗时,且能保证在
leaseTime
内完成。 - 需要避免看门狗线程资源开销的场景。
public void timeBoundedTask() { RLock lock = redisson.getLock("timedLock"); lock.lock(60, TimeUnit.SECONDS); // 60秒后自动释放 try { // 必须在60秒内完成的任务 processTimeSensitiveTask(); } finally { if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); } } }
- 明确知道业务最大耗时,且能保证在
二、tryLock()
方法详解
1. 无参形式:boolean tryLock()
行为:立即尝试获取锁,成功返回
true
,失败返回false
,不阻塞线程。看门狗机制:✅ 支持
默认启用看门狗,锁租期为 30 秒,自动续期。适用场景:
- 非阻塞快速失败场景,如高并发下的缓存重建。
- 需要快速响应,避免线程阻塞。
public void cacheRebuild(String key) { RLock lock = redisson.getLock("cacheLock:" + key); if (lock.tryLock()) { try { // 重建缓存(耗时可控) rebuildCache(key); } finally { if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); } } } else { // 其他线程已处理,直接返回 log.info("缓存正在重建中..."); } }
2. 限时等待:boolean tryLock(long waitTime, TimeUnit unit)
- 行为:在
waitTime
时间内尝试获取锁,超时返回false
,支持线程中断。 - 看门狗机制:✅ 支持
锁租期默认 30 秒,自动续期。 - 适用场景:
- 允许短暂等待的并发控制,如订单处理。
- 平衡系统吞吐量与资源争用。
public void processOrder(String orderId) { RLock lock = redisson.getLock("orderLock:" + orderId); try { if (lock.tryLock(5, TimeUnit.SECONDS)) { // 最多等待5秒 try { // 处理订单 handleOrder(orderId); } finally { if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); } } } else { throw new BusiException("系统繁忙,请重试"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BusiException("操作被中断"); } }
3. 双重时间控制:boolean tryLock(long waitTime, long leaseTime, TimeUnit unit)
- 行为:在
waitTime
时间内尝试获取锁,成功后锁持有leaseTime
时间。 - 看门狗机制:❌ 不支持
显式指定leaseTime
会禁用看门狗。 - 适用场景:
- 精确控制锁生命周期,如定时任务。
- 已知业务耗时的场景,避免自动续期开销。
public void scheduledJob() { RLock lock = redisson.getLock("scheduledJobLock"); try { // 等待10秒获取锁,持有锁30秒 if (lock.tryLock(10, 30, TimeUnit.SECONDS)) { try { // 执行30秒内必须完成的定时任务 executeScheduledTask(); } finally { if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); } } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
三、方法对比与选型指南
1. 看门狗机制支持对比
方法签名 | 看门狗支持 | 阻塞行为 | 锁持有时间控制 |
---|---|---|---|
void lock() |
✅ | 无限阻塞 | 自动续期 |
void lock(long leaseTime, TimeUnit unit) |
❌ | 无限阻塞 | 固定时间 |
boolean tryLock() |
✅ | 非阻塞 | 自动续期 |
boolean tryLock(long waitTime, ...) |
✅ | 限时阻塞 | 自动续期 |
boolean tryLock(long waitTime, leaseTime, ...) |
❌ | 限时阻塞 | 固定时间 |
2. 适用场景对比
场景特性 | 推荐方法 | 理由 |
---|---|---|
严格互斥,耗时不确定 | lock() |
依赖看门狗自动续期,避免锁过期导致数据不一致。 |
已知耗时,需固定锁时间 | lock(leaseTime, unit) |
显式控制锁生命周期,避免看门狗线程开销。 |
非阻塞快速失败 | tryLock() |
立即返回结果,适用于高并发下的缓存击穿防护。 |
允许短暂等待,自动续期 | tryLock(waitTime, unit) |
平衡等待时间和自动续期,适合订单处理等常见业务。 |
精确时间控制,禁用看门狗 | tryLock(waitTime, leaseTime, ...) |
适用于定时任务或批处理,需确保锁在固定时间释放。 |
四、最佳实践与风险规避
1. 看门狗机制的风险控制
风险:若业务逻辑卡死(如死循环),看门狗会无限续期,导致锁无法释放。
规避:
// 设置合理的看门狗超时时间(默认30秒) Config config = new Config(); config.setLockWatchdogTimeout(60_000); // 调整为60秒 RedissonClient redisson = Redisson.create(config);
2. 固定租期(leaseTime
)的陷阱
- 风险:若业务耗时超过
leaseTime
,其他线程可能提前获取锁,导致数据冲突。 - 规避:
// 确保 leaseTime > 业务最大耗时 + 缓冲时间(如20%) long maxBusinessTime = 5000; // 业务最大耗时5秒 lock.tryLock(0, maxBusinessTime * 1200, TimeUnit.MILLISECONDS); // 设置6秒
3. 高并发场景优化
- 问题:大量线程同时争抢锁可能导致 Redis 压力激增。
- 优化:结合随机退避和本地锁:
// 使用本地锁减少Redis请求 private final ConcurrentHashMap<String, Object> localLocks = new ConcurrentHashMap<>(); public void highConcurrencyTask(String key) { Object localLock = localLocks.computeIfAbsent(key, k -> new Object()); synchronized (localLock) { RLock redisLock = redisson.getLock(key); if (redisLock.tryLock(50, TimeUnit.MILLISECONDS)) { try { // 处理业务 } finally { redisLock.unlock(); } } } }
通过合理选择锁方法,结合业务场景和看门狗机制的特性,可以在保证数据一致性的同时,最大化系统性能和可靠性。