欢迎光临
我们一直在努力

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官方文档
  • 分布式锁经典论文
赞(0)
未经允许不得转载:171主机测评 » Redisson分布式锁完全指南
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址