在分布式系统中,为了保证同一时间只有一个线程能执行某段代码(比如秒杀扣库存、定时任务不重复执行),我们必须使用分布式锁。
很多同学在面试时能把 setnx 背得滚瓜烂熟,但一到生产环境,写出来的代码却漏洞百出:
- ❌ 死锁:拿到锁后服务挂了,锁永远不释放。
- ❌ 误删锁:A 线程执行慢,锁过期了;B 线程拿到锁;A 执行完把 B 的锁删了。
- ❌ 原子性问题:判断锁和删除锁不是原子操作。
今天,我们不造轮子,直接上业界最成熟的方案 —— Redisson。它不仅解决了上述所有问题,还提供了**看门狗(Watch Dog)**机制,让你的锁“自动续期”。
🛠️ 为什么选择 Redisson?
相比于自己用 RedisTemplate 封装 setnx,Redisson 提供了像 JDK ReentrantLock 一样简单的 API,同时在底层帮你搞定了:
- 自动续期:业务没执行完,锁不会过期。
- 可重入:同一个线程可以多次加锁。
- 等待重试:获取锁失败时,自动等待并重试,而不是直接返回失败。
1. 🚀 快速上手:最简单的锁
引入依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.24.0</version>
</dependency>
代码实战:
@Autowired
private RedissonClient redisson;
public void killProduct(Long productId) {
String lockKey = "lock:product:" + productId;
RLock lock = redisson.getLock(lockKey);
try {
// 1. 加锁 (阻塞等待,默认过期时间 30s,开启看门狗)
lock.lock();
// 2. 执行业务逻辑 (模拟耗时 40s)
// 因为有看门狗,锁会自动续期,不会因为 30s 到期而释放
Thread.sleep(40000);
// 3. 扣减库存
inventoryService.deduct(productId);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 4. 释放锁 (一定要在 finally 中!)
// 只能释放自己的锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
2. 🐶 核心原理:看门狗 (Watch Dog)
很多同学不理解“看门狗”是啥。简单说,就是Redisson 在后台起的一个定时任务。
- 默认行为:如果你调用 lock() 时没有指定过期时间,Redisson 会默认设置 30 秒过期。
- 续期机制:看门狗每隔 30 / 3 = 10 秒就会检查一下:“哎,这线程还在跑吗?还在跑就给它把锁的时间重置回 30 秒”。
- 作用:防止业务逻辑执行时间超长,导致锁意外过期,被其他线程抢占。
⚠️ 注意:如果你手动指定了过期时间 lock.lock(10, TimeUnit.SECONDS),看门狗机制会失效!锁到期会自动释放。
3. 🛡️ 进阶玩法:尝试锁 (TryLock)
在秒杀等高并发场景,我们不希望线程一直阻塞等待锁,而是希望“抢不到就立刻返回失败,或者等待一小会儿”。
public boolean tryKillProduct(Long productId) {
RLock lock = redisson.getLock("lock:product:" + productId);
try {
// 尝试获取锁
// waitTime: 最多等待 3 秒
// leaseTime: 锁持有 10 秒后强制释放 (看门狗失效!)
boolean isLocked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (isLocked) {
try {
// 拿到锁了,处理业务
inventoryService.deduct(productId);
return true;
} finally {
lock.unlock();
}
} else {
// 没拿到锁,快速失败
return false;
}
} catch (InterruptedException e) {
return false;
}
}
4. 🔗 联锁 (MultiLock) & 红锁 (RedLock)
场景:你需要同时锁定 3 个资源(比如同时扣减 3 个商品的库存),要么全成功,要么全失败。
RLock lock1 = redisson.getLock("lock:product:1");
RLock lock2 = redisson.getLock("lock:product:2");
RLock lock3 = redisson.getLock("lock:product:3");
// 创建联锁
RLock multiLock = redisson.getMultiLock(lock1, lock2, lock3);
try {
// 同时加锁,只要有一个失败,就全部失败
multiLock.lock();
// 业务逻辑...
} finally {
multiLock.unlock();
}
注:RedLock (红锁) 是为了解决 Redis 集群主从切换导致锁丢失的问题,但在实际生产中,RedLock 性能损耗较大且争议较多,通常MultiLock 或 Zookeeper 锁 是更好的替代方案。
5. 读写锁 (ReadWriteLock)
场景:读多写少。比如商品详情,读的时候大家都能读,但写的时候(改价格)必须独占。
RReadWriteLock rwLock = redisson.getReadWriteLock("lock:product:detail");
// 读锁 (共享锁)
rwLock.readLock().lock();
// ... 读取数据 ...
rwLock.readLock().unlock();
// 写锁 (排他锁)
rwLock.writeLock().lock();
// ... 修改数据 ...
rwLock.writeLock().unlock();
💡 架构师建议
- 不要过度依赖分布式锁:锁是性能杀手。能用数据库乐观锁(CAS)、Lua 脚本解决的,尽量别用分布式锁。
- Key 的设计:锁的粒度越细越好。锁 lock:product:1001 比锁 lock:product 性能高一万倍。
- Finally 释放:这是铁律。无论业务是否异常,锁必须释放,否则就是线上事故。
掌握了 Redisson,你就拥有了驾驭分布式并发的“缰绳”。赶紧去优化你的秒杀接口吧!