Java实现Redisson分布式锁的10大陷阱与避坑指南
作者:别掉进我的异常
本文介绍了Redisson分布式锁的基础用法与高级特性,包括基本锁操作、公平锁、读写锁、联锁和红锁等实现方式,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
一、Redisson分布式锁基础用法
1.1 基础依赖配置
<!-- Maven依赖配置 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.18.0</version>
</dependency>yaml
singleServerConfig: address: "redis://127.0.0.1:6379" database: 0 connectionPoolSize: 64 connectionMinimumIdleSize: 10
1.2 基本锁操作
// 获取分布式锁实例
RLock lock = redissonClient.getLock("myLock");
// 方式1:阻塞式加锁(推荐用法)
lock.lock();
try {
// 执行业务逻辑
executeBusiness();
} finally {
// 确保锁被释放
lock.unlock();
}
// 方式2:带超时的尝试加锁
boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (isLocked) {
try {
executeBusiness();
} finally {
lock.unlock();
}
}
// 方式3:异步加锁
RFuture<Void> lockFuture = lock.lockAsync();
lockFuture.whenComplete((result, exception) -> {
try {
executeBusiness();
} finally {
lock.unlockAsync();
}
});二、高级特性使用
2.1 公平锁(Fair Lock)
// 获取公平锁实例(按请求顺序获得锁)
RLock fairLock = redissonClient.getFairLock("fairLock");
fairLock.lock();
try {
// 业务逻辑
} finally {
fairLock.unlock();
}2.2 读写锁(ReadWrite Lock)
// 读写锁实现读多写少场景
RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
// 读锁(共享锁,允许多个线程同时持有)
RLock readLock = rwLock.readLock();
readLock.lock();
try {
// 读取操作
} finally {
readLock.unlock();
}
// 写锁(排他锁,只能有一个线程持有)
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
// 写入操作
} finally {
writeLock.unlock();
}2.3 联锁(MultiLock)
// 同时锁定多个资源
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2, lock3);
multiLock.lock();
try {
// 操作多个资源的业务逻辑
} finally {
multiLock.unlock();
}2.4 红锁(RedLock)
// 红锁(RedLock算法,需要多个Redis实例)
Config config1 = new Config();
config1.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient client1 = Redisson.create(config1);
Config config2 = new Config();
config2.useSingleServer().setAddress("redis://127.0.0.1:6380");
RedissonClient client2 = Redisson.create(config2);
RLock lock1 = client1.getLock("lock");
RLock lock2 = client2.getLock("lock");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2);
redLock.lock();
try {
// 高安全要求的业务逻辑
} finally {
redLock.unlock();
}三、锁失效场景及解决方案
3.1 锁过期时间设置不当
// ❌ 错误示例:锁过期时间太短 lock.lock(5, TimeUnit.SECONDS); // 业务如果超过5秒会出问题 // ✅ 正确做法:合理评估并设置锁超时时间 // 或者使用看门狗机制(默认30秒,每10秒续期) lock.lock(); // 使用看门狗自动续期 // ✅ 或明确指定足够长的超时时间 lock.lock(30, TimeUnit.SECONDS);
3.2 未正确处理锁释放
// ❌ 错误示例:未在finally中释放锁
lock.lock();
executeBusiness();
// 如果这里抛出异常,锁永远不会释放
// ✅ 正确示例:确保锁释放
lock.lock();
try {
executeBusiness();
} finally {
// 检查当前线程是否持有锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}3.3 锁重入问题
// ❌ 错误示例:在递归调用中重复加锁
public void recursiveMethod(int n) {
lock.lock();
try {
if (n > 0) {
recursiveMethod(n - 1); // 重复获取锁,造成死锁
}
} finally {
lock.unlock();
}
}
// ✅ 正确示例:Redisson锁支持可重入
public void recursiveMethod(int n) {
lock.lock();
try {
if (n > 0) {
recursiveMethod(n - 1); // 可重入锁,不会死锁
}
} finally {
lock.unlock();
}
}3.4 集群环境下的特殊问题
// ❌ 错误示例:主从切换导致锁丢失
// 在主从架构中,主节点写入锁后未同步到从节点就宕机
// ✅ 解决方案1:使用红锁(RedLock)
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
// ✅ 解决方案2:使用高可用集群模式
Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://127.0.0.1:7001")
.addNodeAddress("redis://127.0.0.1:7002")
.addNodeAddress("redis://127.0.0.1:7003");四、最佳实践与注意事项
4.1 锁命名规范
// ✅ 使用业务相关的有意义的锁名
// 格式:业务:资源类型:资源标识
String lockKey = "order:pay:lock:" + orderId;
RLock lock = redissonClient.getLock(lockKey);
// ❌ 避免使用通用锁名
RLock badLock = redissonClient.getLock("lock"); // 过于通用,易冲突4.2 避免锁粒度过粗或过细
// ❌ 锁粒度过粗(性能瓶颈)
RLock globalLock = redissonClient.getLock("globalOrderLock");
// ❌ 锁粒度过细(管理复杂)
RLock itemLock = redissonClient.getLock("orderItem:" + itemId + ":" + userId);
// ✅ 合适的锁粒度
RLock orderLock = redissonClient.getLock("order:" + orderId);4.3 超时和重试策略
// 配置合理的重试策略
boolean locked = false;
int retryCount = 0;
int maxRetries = 3;
while (!locked && retryCount < maxRetries) {
try {
locked = lock.tryLock(100, 10000, TimeUnit.MILLISECONDS);
if (locked) {
// 执行业务逻辑
executeBusiness();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} finally {
if (locked) {
lock.unlock();
}
}
retryCount++;
if (!locked && retryCount < maxRetries) {
Thread.sleep(100 * retryCount); // 指数退避
}
}4.4 监控与日志
// 添加锁操作的监控点
@Slf4j
@Component
public class DistributedLockService {
@Autowired
private RedissonClient redissonClient;
public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime,
Supplier<T> supplier) {
RLock lock = redissonClient.getLock(lockKey);
long start = System.currentTimeMillis();
try {
boolean acquired = lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
if (!acquired) {
log.warn("获取锁失败,lockKey: {}", lockKey);
throw new LockAcquireException("获取分布式锁失败");
}
log.info("成功获取锁,lockKey: {}, 耗时: {}ms",
lockKey, System.currentTimeMillis() - start);
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new LockAcquireException("锁获取被中断", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("释放锁,lockKey: {}", lockKey);
}
}
}
}4.5 性能优化建议
# 优化配置示例 lockWatchdogTimeout: 30000 # 看门狗超时时间 keepPubSubOrder: true # 保持订阅顺序 retryInterval: 300 # 重试间隔(ms) retryAttempts: 3 # 重试次数 timeout: 3000 # 命令超时时间(ms)
五、常见问题排查清单
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 锁无故释放 | 业务执行时间超过锁过期时间 | 合理设置leaseTime或使用看门狗 |
| 锁无法释放 | 未在finally中释放锁 | 确保锁释放逻辑在finally块中 |
| 锁竞争激烈 | 锁粒度过粗 | 细化锁粒度,按资源拆分 |
| Redis连接失败 | 网络问题或Redis宕机 | 检查网络,配置连接池和重试机制 |
| 死锁 | 锁重入问题或异常未释放锁 | 使用可重入锁,确保异常时释放锁 |
六、总结
Redisson分布式锁提供了强大的功能和灵活的配置选项,但在使用时需要注意:
- 合理设置超时时间:避免业务未完成锁已过期
- 确保锁必定释放:在finally块中释放锁
- 选择合适的锁类型:根据场景选择普通锁、公平锁、读写锁等
- 注意锁粒度:平衡并发性能和数据安全
- 考虑高可用:在集群环境下使用红锁或集群模式
- 添加监控:记录锁的获取和释放情况,便于排查问题
通过遵循以上最佳实践,可以有效避免分布式锁的常见问题,确保分布式系统的数据一致性和高可用性。
到此这篇关于Java实现Redisson分布式锁的10大陷阱与避坑指南的文章就介绍到这了,更多相关Java Redisson分布式锁陷阱内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
