Redisson RLock比手写SETNX更可靠,因其内置自动续期(watchdog)、可重入、锁释放原子性及线程安全校验,避免了手写易出现的锁误删、超时失效、非原子操作等风险。

Redisson RLock 为什么比手写 SETNX 更可靠
直接用 RLock 是因为 Redisson 封装了自动续期(watchdog)、可重入、锁失效时间与业务执行时间解耦这些关键能力。手写 SETNX + Lua 容易在超时判断、线程中断、节点宕机时漏掉释放或误删其他线程的锁。
常见错误现象:服务重启后锁未释放,或两个实例同时拿到锁,导致库存扣减超卖;又或者任务执行时间超过锁 TTL,锁被自动释放,后续操作写入脏数据。
使用场景包括:订单创建幂等控制、定时任务防重复触发、分布式 ID 生成器协调。
-
RLock底层基于 Redis 的Hash结构存储线程ID和重入次数,不是简单 key-value,所以支持可重入 - watchdog 默认每 10 秒检查一次锁持有者是否还存活,若存活则把 TTL 刷新为 30 秒(可通过
lockWatchdogTimeout配置) - 不建议关闭 watchdog——关掉后就必须严格预估业务耗时并设足够长 TTL,否则风险陡增
Spring Boot 中集成 Redisson 并获取 RLock 的最小必要配置
别碰 RedisTemplate 或 Jedis 手动连 Redis 做锁——Redisson 要求独占连接池,混用会导致连接泄漏或命令错乱。
必须用 RedissonAutoConfiguration 自动装配,核心是提供 RedissonClient Bean:
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("your-pass");
return Redisson.create(config);
}
}
然后在业务类中注入并使用:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
RLock lock = redissonClient.getLock("order:create:uid:123");
try {
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 执行业务
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
-
tryLock(3, 10, TimeUnit.SECONDS)表示最多等待 3 秒,成功后锁自动续期,10 秒后若未手动 unlock 则靠 watchdog 续命 - 务必用
isHeldByCurrentThread()判断再 unlock,避免释放别人持有的锁 - 不要用
@PostConstruct初始化锁对象——RLock是懒加载的,提前 new 出来没意义
tryLock 参数组合对分布式一致性的实际影响
三个参数不是随便填的:第一个是「获取锁的等待时间」,第二个是「锁自动释放时间(leaseTime)」,第三个是单位。它们共同决定系统能否在故障下保持正确性。
典型误配:tryLock(0, 5, SECONDS) —— 等待时间为 0,意味着抢不到就立刻失败,但锁只撑 5 秒,如果业务耗时 6 秒,第 5 秒末锁被释放,下一个线程进来就会并发执行。
- 推荐组合:
tryLock(3, -1, SECONDS),其中-1表示启用 watchdog 自动续期,锁会一直续到业务结束或显式 unlock - 若业务耗时非常稳定(比如固定 800ms),可用
tryLock(1, 2, SECONDS),减少锁残留时间,但必须确保所有路径(含异常分支)都 unlock - 注意:当 leaseTime > 0 时,watchdog 不生效;只有 leaseTime = -1 才开启自动续期
解锁失败、锁被误删、Watchdog 失效的几个真实坑点
最常被忽略的是 JVM 停止过程——比如 K8s 发送 SIGTERM 后,Spring 容器开始销毁 Bean,但业务线程可能还在跑,此时 unlock() 来不及执行,而 watchdog 依赖 Netty 心跳,一旦线程池关闭,心跳断掉,锁会在 leaseTime 后自动释放,导致临界区失控。
另一个高频问题是跨线程 unlock:用 CompletableFuture 异步执行后,在回调里调 unlock(),但 RLock 是线程绑定的,非持有线程调用会抛 IllegalMonitorStateException。
- 务必在同一线程内完成
tryLock和unlock,异步场景改用lockAsync()+unlockAsync()配合CompletableFuture - 加锁前先确认 key 是否有业务语义冲突,比如用
"stock:goodsId:1001"而不是笼统的"stock_lock" - 测试时关掉 watchdog(
config.setLockWatchdogTimeout(0))看是否出现超时释放问题,能快速暴露设计缺陷
watchdog 不是银弹——它依赖客户端心跳存活,网络分区或 Full GC 时间过长都可能导致续期失败。真正高可靠的场景,得配合外部协调服务(如 Etcd)或数据库 for update 做兜底。

















