Redisson API误区和看门狗失效,分布式锁噩梦终结了吗?
摘要:在上一篇《分布式锁的代价与选择:为什么我们最终拥抱了Redisson?》中,我们聊到了手写 SETNX 的'茹毛饮血'时代。既然选择了 Redisson,就意味着我们已经告别了那些让人提心吊胆的死锁噩梦。
写在前面
在上一篇《分布式锁的代价与选择:为什么我们最终拥抱了Redisson?》中,我们聊到了手写 SETNX 的"茹毛饮血"时代。既然选择了 Redisson,就意味着我们已经告别了那些让人提心吊胆的死锁噩梦。
很多时候,我们以为只是调用了一个简单的 lock.lock(),但背后其实是一整套复杂的自动续期、Lua 脚本原子执行和发布订阅机制在默默支撑。
这篇文章不讲虚的,我们从常用的 API 起手,一路通过生产环境的避坑实战,最后钻进底层数据结构与 Lua 源码里,把 Redisson 彻底扒个干干净净。
一、不仅是 Lock 这么简单:核心 API 全景
Redisson 之所以受欢迎,是因为它把分布式锁封装成了我们最熟悉的 java.util.concurrent.locks.Lock 接口风格,极大地降低了学习成本。但除了最基础的 lock(),还有核心功能是你必须掌握的。
1. 基础那把锁:RLock
这是 90% 场景下的默认选择。它对应 Redis 底层的 Hash 结构。
RLock lock = redisson.getLock("order:1001");
lock.lock(); // 阻塞式等待,默认 30秒过期,自带看门狗
try {
// 业务逻辑
} finally {
lock.unlock();
}
2. 更聪明的锁:tryLock (⚡️推荐)
在实际业务中,我们往往不希望线程无限死等,浪费资源。这里有两种常见姿势:
姿势 A:要等待 + 启用看门狗 (最常用)
只指定 waitTime,不指定 leaseTime。这是既想要非阻塞(或有限等待),又想要自动续期的最佳实践。
// 参数1:wait time,我只愿意排队 3秒,拿不到就走人
// 参数2:时间单位
// 重点:没传 leaseTime,所以看门狗机制会自动生效!
boolean res = lock.tryLock(3, TimeUnit.SECONDS);
if (res) {
try {
// 处理业务(哪怕跑 5分钟 也不怕锁过期)
} finally {
lock.unlock();
}
} else {
log.warn("抢锁失败,别挤了!");
}
姿势 B:要等待 + 自动过期 (慎用)
指定了 leaseTime,看门狗会失效。
// 参数1:wait time,排队 3秒
// 参数2:lease time,上锁后 10秒 自动强制释放(注意:指定 leaseTime 会让看门狗失效!)
// 参数3:时间单位
boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (res) {
try {
// 处理业务,必须保证在 10秒 内完成!
} finally {
lock.unlock();
}
}
3. 文明的排队:公平锁 FairLock
默认的锁是非公平的(Non-Fair),线程抢锁全靠 CPU 调度,谁快谁得。但如果你的业务要求"先来后到"(比如抢票排队),请务必使用公平锁。
// 内部利用 Redis 的 List(作为线程等待队列)和 Hash(作为超时记录)实现
RLock fairLock = redisson.getFairLock("ticket:queue");
fairLock.lock();
4. 读多写少的神器:读写锁 ReadWriteLock
这个场景太经典了:商品详情页,读的人多(10000次/秒),改库存的人少(1次/秒)。如果全互斥,性能直接崩盘。
RReadWriteLock rwLock = redisson.getReadWriteLock("product:stock:101");
// 读锁:多个线程可以同时加读锁,只要没有写锁
rwLock.readLock().lock();
// 写锁:必须等所有读锁和写锁都释放了才能加,全互斥
rwLock.writeLock().lock();
5. 联锁 MultiLock (原子性加多把锁)
有时候我们需要同时锁定多个资源,比如"库存"和"余额",要么都锁住,要么都不锁,防止死锁。
