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 (原子性加多把锁) 有时候我们需要同时锁定多个资源,比如"库存"和"余额",要么都锁住,要么都不锁,防止死锁。
阅读全文