一、为什么选择Redisson?
在上一篇文章中,我们通过Redis原生命令实现了分布式锁。但在实际生产环境中,这样的基础方案存在三大痛点:
- 锁续期难题:业务操作超时导致锁提前释放
- 不可重入限制:同一线程无法重复获取已持有的锁
- 高可用风险:单点故障可能导致锁失效
Redisson作为Redis官方推荐的Java客户端,提供了开箱即用的分布式锁实现,其核心优势在于:
- 自动续期机制(看门狗)
- 可重入锁支持
- 多种锁类型(公平锁、联锁等)
- 完善的异常处理
二、快速集成Redisson
2.1 引入依赖
在Maven项目中添加依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.23.2</version>
</dependency>
2.2 配置连接(Spring Boot示例)
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
public class RedissonConfig {
private String host;
private String password;
private int port;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + host + ":" + port)
.setPassword(password)
.setTimeout(3000);
return Redisson.create(config);
}
}
配置说明:
address
格式:redis://IP:PORT 或 rediss://IP:PORT(SSL加密)timeout
:操作超时时间(毫秒)
三、Redisson分布式锁核心API
3.1 基础锁操作
// 获取锁对象
RLock lock = redissonClient.getLock("orderLock");
// 阻塞式获取锁(默认30秒有效期,自动续期)
lock.lock();
// 尝试获取锁(等待10秒,锁持有15秒)
boolean res = lock.tryLock(10, 15, TimeUnit.SECONDS);
// 释放锁
lock.unlock();
3.2 看门狗机制原理
当使用无参lock()
方法时:
- 默认设置锁过期时间30秒
- 启动后台线程每10秒检查一次
- 如果线程仍持有锁,自动续期到30秒
3.3 三种加锁方式对比
方法签名 | 特点 | 适用场景 |
---|---|---|
void lock() | 阻塞等待,自动续期 | 长期持有锁场景 |
boolean tryLock() | 立即返回结果 | 快速失败场景 |
boolean tryLock(long waitTime, …) | 可设置等待时间,手动控制有效期 | 精确控制锁持有时间的业务 |
四、实战:司机抢单系统
4.1 业务场景分析
假设网约车系统中:
- 多个司机同时抢同一个订单
- 订单状态变更需要原子操作
- 高并发下需保证数据一致性
4.2 分布式锁实现方案
public Boolean robOrder(Long driverId, Long orderId) {
final String lockKey = RedisConstant.ROB_ORDER_LOCK + orderId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁(最多等待2秒,锁持有5秒)
if (lock.tryLock(2, 5, TimeUnit.SECONDS)) {
// 二次校验订单状态
if (!redisTemplate.hasKey(ORDER_AVAILABLE_KEY)) {
throw new BusinessException("订单已被抢");
}
// 执行核心业务逻辑
updateOrderStatus(driverId, orderId);
// 移除可用标记
redisTemplate.delete(ORDER_AVAILABLE_KEY);
return true;
}
return false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("系统中断异常");
} finally {
// 仅释放当前线程持有的锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
4.3 关键代码解析
锁命名策略:
- 使用
订单ID
作为锁后缀,实现细粒度控制 - 示例:
rob_order_lock:20230815001
- 使用
双重检查机制:
- 获取锁前快速失败
- 获取锁后再次校验业务状态
异常处理规范:
- 正确处理InterruptedException
- finally块确保锁释放
五、生产环境最佳实践
5.1 锁使用原则
原则 | 说明 | 反例 |
---|---|---|
最小作用域原则 | 锁的粒度尽可能小 | 对整个系统使用全局锁 |
快速释放原则 | 业务完成后立即释放锁 | 持有锁进行网络调用等耗时操作 |
异常处理原则 | finally块确保锁释放 | 未处理异常导致死锁 |
避免嵌套原则 | 谨慎使用嵌套锁 | 多层级锁增加死锁风险 |
5.2 常见问题解决方案
问题一:锁续期时间设置不合理
- 症状:频繁出现锁过期
- 方案:根据业务耗时动态调整
// 根据历史统计设置合理值
long leaseTime = calculateBusinessTime() + 5; // 增加5秒缓冲
lock.tryLock(2, leaseTime, TimeUnit.SECONDS);
问题二:客户端时钟不同步
- 症状:锁提前/延后释放
- 方案:启用NTP时间同步服务
问题三:Redis集群故障转移
- 症状:脑裂导致多客户端持有锁
- 方案:使用RedLock算法
RLock lock1 = redissonClient1.getLock("lock");
RLock lock2 = redissonClient2.getLock("lock");
RLock lock3 = redissonClient3.getLock("lock");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock();
六、性能优化建议
- 连接池配置
// 在Redisson配置中优化连接参数
config.useSingleServer()
.setConnectionPoolSize(64) // 连接池大小
.setConnectionMinimumIdleSize(24); // 最小空闲连接
- 监控指标收集
- 锁等待时间
- 锁持有时间
- 锁竞争频率
- 压力测试方案
// 使用JMeter模拟并发场景
ThreadGroup:
Number of Threads: 100
Ramp-Up Period: 5s
Loop Controller:
Loop Count: Forever
七、扩展应用场景
7.1 分布式限流
RRateLimiter rateLimiter = redisson.getRateLimiter("apiLimit");
// 每秒处理10个请求
rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);
7.2 分布式信号量
RSemaphore semaphore = redisson.getSemaphore("parkingSlot");
// 获取停车位(最多等待10秒)
if(semaphore.tryAcquire(10, TimeUnit.SECONDS)) {
try {
// 停车操作
} finally {
semaphore.release();
}
}
八、总结与展望
通过Redisson实现分布式锁,开发者可以:
- 避免手动处理复杂边界条件
- 获得生产级的可靠性保证
- 轻松扩展更多分布式功能