Java Redisson分布式锁实战案例与避坑指南
📑 目录导读
- 为什么需要分布式锁?Redisson锁的核心价值
- Redisson锁的8种常见模式与配置
- 实战案例:秒杀系统、重复提交、定时任务
- 必须避开的5大雷区
- 高频问答Q&A(含代码演示)
引言:单机锁的“天花板”与分布式锁的诞生
在单体架构中,Java开发者通过synchronized或ReentrantLock即可轻松解决并发问题,但当系统拆分为微服务、部署在多台服务器上时,两个线程可能分别运行在不同的JVM中,此时单机锁便彻底失效——这便是分布式锁的用武之地。

Redisson锁(基于Redis的Java客户端)提供了可重入、高可用、支持Watchdog自动续期的分布式锁,已成为业界标准方案,本文将结合真实案例,剖析其核心用法。
第一部分:Redisson锁的8种模式与配置
1 基础配置(Spring Boot整合)
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
2 锁模式速查表
| 模式 | 方法名 | 适用场景 |
|---|---|---|
| 可重入锁 | getLock() |
业务方法内部递归调用 |
| 公平锁 | getFairLock() |
需要排队顺序执行业务 |
| 联锁 | getMultiLock() |
多个Redis节点同时加锁 |
| 红锁 | getRedLock() |
高一致性要求场景 |
| 读写锁 | getReadWriteLock() |
读多写少场景 |
| 信号量 | getSemaphore() |
限流控制 |
| 闭锁 | getCountDownLatch() |
等待N个条件完成 |
| 自旋锁 | tryLock(5, 3, TimeUnit.SECONDS) |
快速失败场景 |
第二部分:实战案例(代码级解析)
秒杀系统库存扣减(最经典)
RLock lock = redissonClient.getLock("seckill:stock:1001");
try {
// 尝试加锁,等待3秒,持有锁30秒(Watchdog自动续期)
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
int stock = getStock(1001);
if (stock > 0) {
deductStock(1001);
return "抢购成功";
} else {
return "库存不足";
}
} else {
return "系统繁忙,请稍后重试";
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "系统异常";
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
作用:防止高并发下超卖,Watchdog会在业务执行期间自动续期,避免锁提前释放。
防止接口重复提交
// 用户id + 业务id作为锁标识
String lockKey = "submit:order:" + userId + ":" + orderId;
RLock lock = redissonClient.getLock(lockKey);
boolean acquired = lock.tryLock(0, 10, TimeUnit.SECONDS);
if (!acquired) {
throw new BusinessException("请勿重复提交");
}
try {
processOrder();
} finally {
lock.unlock();
}
技巧:tryLock(0,...)表示立即尝试,不等待,适合对响应时间敏感的接口。
分布式定时任务分布式协调
@Scheduled(cron = "0 0 2 * * ?")
public void runDailyTask() {
RLock lock = redissonClient.getLock("cron:data-sync");
if (lock.tryLock(0, 30, TimeUnit.MINUTES)) {
try {
// 执行业务,如同步数据
} finally {
lock.unlock();
}
}
}
注意:单机定时任务+分布式锁=集群中只有一个任务执行,避免重复触发。
第三部分:必须避开的5大雷区
❌ 雷区1:忘记解锁导致死锁
lock.lock(); // 业务抛异常 -> 直接退出 -> 未解锁
解法:必须放在finally块中,或使用tryLock并捕获异常。
❌ 雷区2:锁粒度太粗
// 错误:锁定整个商品类别
lock("category:update");
// 正确:只锁定单个商品
lock("product:update:" + productId);
❌ 雷区3:Watchdog失效(高版本Redisson需配置)
redisson: lock-watchdog-timeout: 30000 # 默认30秒
原理:Watchdog每10秒检查一次,如果业务未结束则续期,若业务耗时>看门狗续期间隔,需手动调整。
❌ 雷区4:Redis集群脑裂导致锁丢失
解法:使用RedLock(红锁),但性能会下降30%~50%。
❌ 雷区5:锁超时时间设置过短
// 业务可能执行2分钟,但锁超时设了10秒 -> 锁提前释放
解法:合理估算业务耗时+Watchdog兜底。
第四部分:高频问答Q&A(附代码)
Q1:Redisson的lock()和tryLock()有什么区别?
| 方法 | 特点 | 代码示例 |
|---|---|---|
lock() |
阻塞等待,直到获取锁 | 适合需要必须获取锁的场景 |
tryLock() |
尝试获取,可设置等待时间 | 适合超时放弃的场景 |
Q2:如何测试分布式锁是否生效?
使用JMeter并发请求秒杀接口,观察日志:
// 在加锁前后打印线程id
log.info("尝试获取锁,线程id: {}", Thread.currentThread().getId());
若不同线程的锁标识冲突,则输出成功。
Q3:锁的key如何设计更合理?
推荐格式: 业务域:资源类型:资源ID:操作
order:create:user-{userId} // 用户维度
seckill:stock:{productId} // 商品维度
避免使用IP或动态字符串,防止key冲突。
Q4:锁升级后需要重新加锁吗?
答:不需要。RLock是可重入的,同一线程可多次加锁(计数递增),解锁时递减。
用好Redisson锁的三个原则
- 锁粒度最小化:越细,并发能力越高
- 超时设置合理化:业务耗时+20%余量
- 异常处理完整化:try-catch-finally / tryLock自旋
最后建议:在非极端高一致性场景(如点赞、统计),考虑用Redis的SET NX+Lua脚本替代完整锁,以降低延迟;但涉及资金、库存等关键业务,务必使用Redisson的完整锁机制。
参考资料:Redisson官方文档、阿里巴巴Java开发手册(分布式锁章节)、Stack Overflow高频问答整理