Redisson分布式锁完全指南
一份从理论到实践的完整教程,帮助你快速掌握Redisson分布式锁的使用
1. 快速入门
1.1 什么是分布式锁?
简单来说:在多个服务器之间实现"同一时刻只有一个线程能执行某段代码"
为什么需要?
场景:秒杀活动
├── 服务器A:用户1购买 → 库存-1 → 剩余99
├── 服务器B:用户2购买 → 库存-1 → 剩余99
└── 结果:库存应为98,实际却是99 → ❌ 数据不一致
分布式锁的作用:保证所有服务器上,同一时刻只有一个请求能修改库存
1.2 为什么选择Redisson?
| 对比项 | Redisson | 其他方案 |
|---|---|---|
| 上手难度 | ⭐ 简单 | ⭐⭐⭐ 较难 |
| 功能完整性 | ⭐⭐⭐⭐⭐ 全面 | ⭐⭐⭐ 有限 |
| 可靠性 | ⭐⭐⭐⭐⭐ 看门狗机制 | ⭐⭐⭐ 需自行处理 |
| 维护成本 | ⭐ 低 | ⭐⭐⭐ 较高 |
1.3 3分钟快速体验
// 1. 添加依赖(Maven)
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.24.3</version>
</dependency>
// 2. 配置Redisson
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
// 3. 使用分布式锁
@Autowired
private RedissonClient redissonClient;
public void doSomething() {
RLock lock = redissonClient.getLock("myLock");
try {
lock.lock(); // 获取锁
// 执行业务代码
} finally {
lock.unlock(); // 释放锁
}
}
2. 核心概念
2.1 工作原理图解
分布式锁获取流程
┌─────────────┐
│ 请求加锁 │
└──────┬──────┘
│
▼
┌─────────────────────┐
│ Redis SETNX 命令 │
│ SET key value NX │
└──────┬──────────────┘
│
├─ 成功 ──► 启动看门狗(自动续期)
│ │
│ ▼
│ ┌─────────┐
│ │ 执行业务 │
│ └────┬────┘
│ │
│ ▼
│ ┌─────────┐
│ │ 释放锁 │
│ └─────────┘
│
└─ 失败 ──► 等待/返回失败
2.2 看门狗机制(重点)
问题:业务执行时间 > 锁超时时间怎么办?
看门狗解决方案:
锁设置30秒过期
├── 看门狗每10秒检查一次
├── 如果锁还被持有 → 自动续期30秒
└── 循环直到锁被释放
代码示例:
// ❌ 错误:固定过期时间,看门狗不生效
lock.lock(10, TimeUnit.SECONDS); // 10秒后锁会自动过期
// ✅ 正确:不设置过期时间,启用看门狗
lock.lock(); // 看门狗会自动续期
2.3 锁类型速查表
| 锁类型 | 适用场景 | 并发性能 | 代码示例 |
|---|---|---|---|
| 普通锁 | 一般互斥访问 | ⭐⭐⭐ | getLock("key") |
| 公平锁 | 需要按顺序执行 | ⭐⭐ | getFairLock("key") |
| 读写锁 | 读多写少场景 | ⭐⭐⭐⭐ | getReadWriteLock("key") |
| 信号量 | 限流、资源池管理 | ⭐⭐⭐⭐ | getSemaphore("key") |
| 联锁 | 需要同时锁多个资源 | ⭐⭐ | getMultiLock(lock1, lock2) |
3. 实战场景
场景1:秒杀防超卖
需求:100个商品,1000人抢购,防止超卖
public boolean seckill(String productId, String userId) {
// 为每个商品创建独立的锁
RLock lock = redissonClient.getLock("seckill:" + productId);
try {
// 等待3秒获取锁,锁10秒自动释放
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
int stock = productStockService.getStock(productId);
if (stock > 0) {
// 扣减库存
productStockService.decreaseStock(productId, 1);
// 创建订单
orderService.createOrder(productId, userId);
return true;
}
}
return false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放前检查是否为自己持有的锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
场景2:定时任务防重复执行
需求:集群环境下,确保定时任务只在一个节点执行
@Scheduled(cron = "0 */5 * * * ?") // 每5分钟执行一次
public void executeTask() {
RLock lock = redissonClient.getLock("scheduled:task:data-sync");
try {
// 非阻塞获取锁
if (lock.tryLock()) {
dataSyncService.syncData(); // 只有获取到锁的节点才执行
} else {
log.info("其他节点正在执行任务,跳过本次执行");
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
场景3:缓存防击穿(缓存重建)
问题:缓存失效瞬间,大量请求同时打到数据库
解决方案:使用分布式锁 + 双重检查
public Product getProduct(String productId) {
// 1. 先查缓存
Product product = cacheService.get(productId);
if (product != null) {
return product;
}
RLock lock = redissonClient.getLock("cache:" + productId);
try {
// 2. 获取锁
if (lock.tryLock()) {
// 3. 再次检查缓存(双重检查)
product = cacheService.get(productId);
if (product == null) {
// 4. 查数据库
product = productRepository.findById(productId);
// 5. 写入缓存
cacheService.set(productId, product, 30, TimeUnit.MINUTES);
}
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return product;
}
执行流程:
请求A、B、C同时到达
├── 都发现缓存失效
├── 都尝试获取锁
├── A获取锁成功 → B、C等待
├── A查数据库 → 写入缓存 → 释放锁
├── B获取锁 → 再次检查缓存 → 发现已有数据 → 直接返回
└── C同理
结果:数据库只被访问1次 ✅
4. 代码实战
4.1 基础用法
用法1:阻塞式获取锁
RLock lock = redissonClient.getLock("myLock");
try {
lock.lock(); // 一直等待,直到获取到锁
// 执行业务逻辑
doBusiness();
} finally {
lock.unlock();
}
用法2:带超时的获取锁
RLock lock = redissonClient.getLock("myLock");
try {
// 等待3秒,最多持有10秒
boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (acquired) {
try {
doBusiness();
} finally {
lock.unlock();
}
} else {
// 获取锁失败的处理
log.warn("获取锁失败,请稍后重试");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
参数说明:
waitTime:愿意等待多久获取锁leaseTime:锁持有时间(超过自动释放)
用法3:看门狗模式(推荐)
RLock lock = redissonClient.getLock("myLock");
try {
lock.lock(); // 不设置过期时间,启用看门狗
// 即使业务执行很久,锁也会自动续期
longRunningTask();
} finally {
lock.unlock();
}
4.2 高级用法
读写锁(读多写少场景)
RReadWriteLock rwLock = redissonClient.getReadWriteLock("cacheLock");
// 读操作(多个线程可以同时读)
public String readData(String key) {
RLock readLock = rwLock.readLock();
try {
readLock.lock();
return cacheService.get(key);
} finally {
readLock.unlock();
}
}
// 写操作(互斥)
public void writeData(String key, String value) {
RLock writeLock = rwLock.writeLock();
try {
writeLock.lock();
cacheService.set(key, value);
} finally {
writeLock.unlock();
}
}
性能对比:
| 场景 | 普通锁 | 读写锁 |
|---|---|---|
| 10个读请求 | 串行,慢 | 并发,快 |
| 1个写 + 9个读 | 全部串行 | 写互斥,读并发 |
公平锁(保证顺序)
// 普通锁:非公平,抢占式
RLock lock = redissonClient.getLock("normalLock");
// 公平锁:按请求顺序获取
RLock fairLock = redissonClient.getFairLock("fairLock");
try {
fairLock.lock(); // 保证先到先得
doBusiness();
} finally {
fairLock.unlock();
}
信号量(限流场景)
// 假设某个资源最多允许10个线程同时访问
RSemaphore semaphore = redissonClient.getSemaphore("resource-limit");
semaphore.trySetPermits(10); // 初始化许可证数量
try {
semaphore.acquire(); // 获取许可证
// 访问受限资源
accessLimitedResource();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 释放许可证
}
应用场景:
- 数据库连接池(限制最大连接数)
- API限流(控制并发访问)
- 资源池管理
联锁(同时锁多个资源)
RLock lock1 = redissonClient.getLock("user:123");
RLock lock2 = redissonClient.getLock("account:456");
// 创建联锁
RLock multiLock = redissonClient.getMultiLock(lock1, lock2);
try {
// 同时获取两把锁
multiLock.lock();
// 执行需要同时操作用户和账户的业务
transferMoney();
} finally {
multiLock.unlock(); // 一起释放
}
5. 避坑指南
⚠️ 坑1:锁粒度设计不合理
错误示例
// ❌ 粒度过细:性能差
public void updateUser(User user) {
lock("user:name:" + user.getId());
user.setName(newName);
unlock("user:name:" + user.getId());
lock("user:age:" + user.getId());
user.setAge(newAge);
unlock("user:age:" + user.getId());
}
// ❌ 粒度过粗:影响其他用户
public void updateUser(User user) {
lock("user:table"); // 锁住整张表
userService.update(user);
unlock("user:table");
}
正确做法
// ✅ 粒度合适:按业务维度
public void updateUser(User user) {
String lockKey = "user:" + user.getId();
lock(lockKey);
try {
userService.update(user);
} finally {
unlock(lockKey);
}
}
⚠️ 坑2:死锁
典型场景:转账操作
// ❌ 会死锁的代码
public void transfer(String fromUser, String toUser, BigDecimal amount) {
lock("user:" + fromUser);
lock("user:" + toUser); // 可能死锁
// ...
}
死锁产生过程:
线程A:转账 A→B,先锁A,再锁B
线程B:转账 B→A,先锁B,再锁A
时间线:
├── T1:线程A 锁住 user:A
├── T2:线程B 锁住 user:B
├── T3:线程A 尝试锁 user:B ❌ 被阻塞
├── T4:线程B 尝试锁 user:A ❌ 被阻塞
└── 结果:死锁
解决方案
方案1:按固定顺序加锁
// ✅ 解决方案:按ID排序,固定加锁顺序
public void transfer(String fromUser, String toUser, BigDecimal amount) {
// 确保总是先锁ID小的
String firstId = fromUser.compareTo(toUser) < 0 ? fromUser : toUser;
String secondId = fromUser.compareTo(toUser) < 0 ? toUser : fromUser;
lock("user:" + firstId);
lock("user:" + secondId);
try {
doTransfer(fromUser, toUser, amount);
} finally {
unlock("user:" + secondId);
unlock("user:" + firstId);
}
}
方案2:设置超时时间
// ✅ 解决方案:设置超时,避免永久等待
public void transfer(String fromUser, String toUser, BigDecimal amount) {
try {
if (tryLock("user:" + fromUser, 5, TimeUnit.SECONDS) &&
tryLock("user:" + toUser, 5, TimeUnit.SECONDS)) {
try {
doTransfer(fromUser, toUser, amount);
} finally {
unlock("user:" + toUser);
unlock("user:" + fromUser);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
⚠️ 坑3:锁没有释放
错误示例
// ❌ 异常导致锁没有释放
public void businessMethod() {
RLock lock = redissonClient.getLock("myLock");
lock.lock();
doBusiness(); // 如果这里抛异常,锁永远不会释放
lock.unlock();
}
正确做法
// ✅ 使用finally确保释放
public void businessMethod() {
RLock lock = redissonClient.getLock("myLock");
try {
lock.lock();
doBusiness();
} catch (BusinessException e) {
log.error("业务异常", e);
throw e;
} finally {
// 再检查一次,确保是当前线程持有的锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
⚠️ 坑4:误释放其他线程的锁
错误示例
// ❌ 可能释放其他线程的锁
public void businessMethod() {
RLock lock = redissonClient.getLock("myLock");
try {
lock.lock();
doBusiness();
} finally {
lock.unlock(); // 如果锁已过期,可能释放其他线程的锁
}
}
正确做法
// ✅ 释放前检查
public void businessMethod() {
RLock lock = redissonClient.getLock("myLock");
try {
lock.lock();
doBusiness();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
⚠️ 坑5:锁Key命名混乱
错误示例
// ❌ 命名不规范,容易冲突
redissonClient.getLock("lock");
redissonClient.getLock("myLock");
redissonClient.getLock("userlock");
正确做法
// ✅ 统一管理锁Key
public class LockKeys {
// 格式:业务:模块:资源:ID
public static final String USER = "business:user:%s";
public static final String ORDER = "business:order:%s";
public static final String PRODUCT = "business:product:%s";
}
// 使用时
String lockKey = String.format(LockKeys.USER, userId);
RLock lock = redissonClient.getLock(lockKey);
6. 最佳实践
6.1 设计原则
| 原则 | 说明 | 示例 |
|---|---|---|
| 最小锁粒度 | 只锁必要的资源 | 更新用户信息只锁该用户 |
| 最短持有时间 | 尽快释放锁 | 锁内只做核心逻辑 |
| 必有超时 | 避免永久等待 | 使用tryLock设置超时 |
| 必有释放 | finally块释放 | 确保锁一定被释放 |
6.2 性能优化技巧
技巧1:减少锁的持有时间
// ❌ 错误:锁内做耗时操作
public void badPractice() {
lock();
longRunningTask1(); // 耗时操作
longRunningTask2(); // 耗时操作
unlock();
}
// ✅ 正确:锁内只做核心操作
public void goodPractice() {
// 准备数据(不需要锁)
Data data = prepareData();
lock();
// 只在锁内执行核心逻辑
coreOperation(data);
unlock();
// 后续处理(不需要锁)
postProcess(data);
}
技巧2:使用读写锁提升读性能
// 适用于:读多写少(如缓存)
public class CacheService {
private RReadWriteLock rwLock = redissonClient.getReadWriteLock("cache");
// 多个读请求可以并发执行
public String get(String key) {
rwLock.readLock().lock();
try {
return cache.get(key);
} finally {
rwLock.readLock().unlock();
}
}
// 写请求互斥
public void set(String key, String value) {
rwLock.writeLock().lock();
try {
cache.set(key, value);
} finally {
rwLock.writeLock().unlock();
}
}
}
技巧3:合理设置超时时间
// 公式:超时时间 = 业务平均执行时间 × 1.5
public void optimizedTimeout() {
// 假设业务平均执行10秒
long businessTime = 10000; // 10秒
// 设置15秒超时(10秒 × 1.5)
long timeout = businessTime * 15 / 10;
try {
if (lock.tryLock(3, timeout, TimeUnit.MILLISECONDS)) {
try {
doBusiness();
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
6.3 监控与告警
关键指标
public class LockMonitor {
// 监控锁等待时间
public void monitorLockWait(RLock lock, String operation) {
long startTime = System.currentTimeMillis();
try {
lock.lock();
} finally {
long waitTime = System.currentTimeMillis() - startTime;
if (waitTime > 1000) { // 超过1秒告警
log.warn("锁等待时间过长: operation={}, waitTime={}ms",
operation, waitTime);
// 发送告警
alertService.sendAlert("锁等待超时", operation, waitTime);
}
}
}
// 监控锁持有时间
public void monitorLockHold(RLock lock, String operation, Runnable task) {
lock.lock();
long startTime = System.currentTimeMillis();
try {
task.run();
} finally {
long holdTime = System.currentTimeMillis() - startTime;
if (holdTime > 5000) { // 超过5秒告警
log.warn("锁持有时间过长: operation={}, holdTime={}ms",
operation, holdTime);
}
lock.unlock();
}
}
}
6.4 常用工具类封装
/**
* 分布式锁工具类
*/
public class DistributedLockUtils {
@Autowired
private RedissonClient redissonClient;
/**
* 执行带锁的业务
* @param lockKey 锁的key
* @param waitTime 等待时间
* @param leaseTime 持有时间
* @param task 业务逻辑
* @return 是否执行成功
*/
public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime,
Supplier<T> task) {
RLock lock = redissonClient.getLock(lockKey);
try {
boolean acquired = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
if (!acquired) {
throw new BusinessException("获取锁失败: " + lockKey);
}
try {
return task.get();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("获取锁被中断", e);
}
}
/**
* 简化版本:不设置超时,使用看门狗
*/
public <T> T executeWithLock(String lockKey, Supplier<T> task) {
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock();
try {
return task.get();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
} catch (Exception e) {
throw new BusinessException("锁执行异常", e);
}
}
}
// 使用示例
@Service
public class OrderService {
@Autowired
private DistributedLockUtils lockUtils;
public void createOrder(String productId) {
String lockKey = "order:create:" + productId;
lockUtils.executeWithLock(lockKey, () -> {
// 业务逻辑
checkStock(productId);
decreaseStock(productId);
saveOrder();
});
}
}
📚 总结与对比
Redisson分布式锁优缺点
| 维度 | 优点 | 缺点 |
|---|---|---|
| 易用性 | API简洁,学习成本低 | 高级特性需要理解 |
| 功能性 | 支持多种锁类型 | 过于全面可能混淆 |
| 可靠性 | 看门狗自动续期 | 依赖Redis可用性 |
| 性能 | 基于Redis,高性能 | 网络开销 |
| 扩展性 | 支持集群模式 | 集群成本较高 |
适用场景判断
需要使用分布式锁的场景:
✅ 多个服务实例操作同一资源
✅ 需要保证数据一致性
✅ 高并发环境
✅ 分布式任务调度
不需要分布式锁的场景:
❌ 单机应用(用本地锁即可)
❌ 读多写少(可用读写锁)
❌ 对一致性要求不高(可用乐观锁)
🔗 参考资源
- Redisson官方文档
- Redis官方文档
- 分布式锁经典论文











