1. Redisson分布式锁概述
在分布式系统中,多个服务实例对共享资源的并发访问是一个常见挑战。传统单机锁机制无法解决跨进程的同步问题,这时就需要引入分布式锁。Redisson作为基于Redis的Java客户端,提供了一套完善的分布式锁实现方案。
Redis之所以适合实现分布式锁,主要基于以下特性:
- 单线程执行模型保证原子性操作
- 高性能的键值存储
- 支持键过期自动删除
- 丰富的原子性命令
相比自己实现Redis分布式锁,Redisson提供了更高级的封装:
- 可重入锁支持
- 自动续期机制(看门狗)
- 多种锁类型(公平锁、联锁等)
- 完善的异常处理
2. 环境准备与配置
2.1 依赖引入
首先需要在项目中添加Redisson依赖。对于Spring Boot项目,推荐使用redisson-spring-boot-starter:
xml复制<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.4</version>
</dependency>
注意:版本选择应与你的Spring Boot版本匹配。较新的Spring Boot 3.x建议使用3.23.x以上版本。
2.2 客户端配置
Redisson支持多种配置方式,以下是单节点Redis的典型配置:
java复制@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Value("${spring.redis.password}")
private String redisPassword;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + redisHost + ":" + redisPort)
.setPassword(redisPassword)
.setDatabase(0)
.setConnectionPoolSize(64)
.setConnectionMinimumIdleSize(10)
.setTimeout(3000);
return Redisson.create(config);
}
}
关键配置参数说明:
connectionPoolSize:最大连接数,根据业务并发量调整connectionMinimumIdleSize:最小空闲连接数timeout:操作超时时间(毫秒)
对于生产环境,建议使用集群模式:
java复制config.useClusterServers()
.addNodeAddress("redis://node1:6379", "redis://node2:6379")
.setScanInterval(2000); // 集群状态扫描间隔
3. 基础锁使用
3.1 可重入锁实现
Redisson最基本的锁类型是RLock(可重入锁),使用方式如下:
java复制@Autowired
private RedissonClient redissonClient;
public void doSomething() {
RLock lock = redissonClient.getLock("resourceLock");
try {
// 尝试获取锁,等待最多100秒,锁自动释放时间30秒
boolean locked = lock.tryLock(100, 30, TimeUnit.SECONDS);
if (locked) {
// 执行业务逻辑
processBusiness();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("获取锁被中断", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
3.2 关键参数解析
tryLock方法的三个重要参数:
waitTime:获取锁的最大等待时间- 设置为0表示不等待,立即返回
- 设置为-1表示一直等待直到获取锁
leaseTime:锁的持有时间- 超过此时间锁会自动释放
- 设置为-1启用看门狗自动续期机制
unit:时间单位
最佳实践:生产环境建议设置合理的waitTime,避免线程长时间阻塞。leaseTime应根据业务执行时间合理设置,或使用-1启用自动续期。
4. 高级特性解析
4.1 可重入锁原理
Redisson的可重入锁通过Redis的Hash结构实现,存储结构示例:
code复制myLock: {
"uuid1:threadId1": 2, // 重入次数
"uuid2:threadId2": 1
}
加锁的Lua脚本逻辑:
- 检查锁是否存在
- 不存在则设置hash字段,初始化重入次数为1
- 存在则检查是否为当前线程持有,是则重入次数+1
- 设置过期时间
这种设计保证了:
- 同一线程可重复获取锁
- 锁的持有者信息明确
- 避免死锁(有过期时间)
4.2 看门狗机制
当leaseTime设置为-1时,Redisson会启动看门狗线程定期(默认10秒)检查锁状态并续期。关键点:
-
续期逻辑:
- 只有持有锁的客户端会启动看门狗
- 每次续期将锁的过期时间重置为30秒(默认)
-
配置参数:
java复制config.setLockWatchdogTimeout(30000); // 修改默认30秒的续期时间 -
注意事项:
- 看门狗线程是后台守护线程
- 应用关闭时需要正确释放锁,否则可能导致锁无法及时释放
4.3 公平锁实现
Redisson还提供了公平锁(FairLock),特点:
- 按照请求顺序获取锁
- 基于Redis队列实现
- 适合对公平性要求高的场景
使用方式:
java复制RLock fairLock = redissonClient.getFairLock("fairLock");
5. 生产实践与问题排查
5.1 常见问题解决方案
-
锁等待时间过长
- 检查业务逻辑是否过于复杂
- 评估锁粒度是否合适
- 考虑使用tryLock替代lock
-
锁无法释放
- 确保finally块中释放锁
- 检查网络连接是否正常
- 监控Redis内存和CPU使用率
-
锁被误释放
- 总是检查isHeldByCurrentThread()
- 为不同业务使用不同的锁名称
5.2 性能优化建议
-
锁粒度控制:
- 细粒度锁(如用户ID级别)比粗粒度锁(如全局锁)性能更好
- 但要注意避免死锁风险
-
锁超时设置:
java复制// 推荐设置 lock.tryLock(500, 10000, TimeUnit.MILLISECONDS); -
监控指标:
- 锁获取成功率
- 平均等待时间
- 锁持有时间
5.3 主从切换问题解决方案
Redis主从架构下,主节点崩溃可能导致锁丢失。Redisson提供RedLock算法解决方案:
java复制RLock lock1 = redissonClient1.getLock("lock");
RLock lock2 = redissonClient2.getLock("lock");
RLock lock3 = redissonClient3.getLock("lock");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
redLock.lock();
// 执行业务逻辑
} finally {
redLock.unlock();
}
实现原理:
- 向多个独立Redis实例获取锁
- 当获得多数锁时认为成功
- 总获取时间应小于锁自动释放时间
6. 最佳实践总结
-
锁命名规范:
- 使用业务相关的明确名称
- 避免使用通用名称如"lock"
- 示例:"order:pay:" + orderId
-
异常处理:
java复制try { lock.lock(); // 业务代码 } catch (Exception e) { // 特定异常处理 } finally { safeUnlock(lock); } private void safeUnlock(RLock lock) { try { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } catch (IllegalMonitorStateException e) { log.warn("锁状态异常", e); } } -
调试技巧:
- 使用Redis命令查看锁状态:
code复制HGETALL myLock TTL myLock - 开启Redisson调试日志:
properties复制logging.level.org.redisson=DEBUG
- 使用Redis命令查看锁状态:
在实际项目中,我曾遇到一个典型场景:支付回调处理。多个支付渠道的回调可能同时到达,需要保证对同一订单的顺序处理。使用Redisson分布式锁后,不仅解决了并发问题,还能通过锁的等待时间设置实现简单的流量控制。关键点在于合理设置锁的等待时间和持有时间,既要避免长时间阻塞,又要保证业务逻辑完整执行。