续期的无限套娃
scheduleExpirationRenewal最终会调用renewExpiration:
private void renewExpiration() { // 这里的 1/3 是硬编码的规则 // 默认 lockWatchdogTimeout 是 30000ms // 所以每 10000ms 执行一次 Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { // 执行 Lua 脚本,把 ttl 重新刷回 30秒 RFuture<Boolean> future = renewExpirationAsync(threadId); future.onComplete((res, e) -> { if (res) { // 如果续期成功,这就形成了递归调用:自己调自己 renewExpiration(); } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); }核心逻辑总结:
- 三分之一原则:每隔锁超时时间的 1/3(默认10秒),检查一次。
- 无限递归:只要检查到锁还在,就重置过期时间,并注册下一次检查。
- 生死绑定:这个任务跑在客户端进程里,如果客户端宕机,任务停止,Redis 里的锁在 30秒 后自动过期。
四、我在生产环境踩过的坑:避坑实战
API 谁都会调,但能避开坑的才是老司机。这六个坑,都是真金白银换来的教训。
💣 陷阱一:好心办坏事 —— 弄死看门狗
这是新手最容易犯的错。
❌ 错误姿势:
// 我怕死锁,所以强行指定 10秒 过期 lock.lock(10, TimeUnit.SECONDS); // 或者 lock.tryLock(1, 10, TimeUnit.SECONDS);⚠️ 后果:
Redisson 的看门狗(WatchDog)机制只有在你未指定锁过期时间时才会生效!
一旦你手动传了leaseTime,Redisson 就会认为你有自己的想法,不再插手。如果你的业务因为数据库卡顿跑了 15秒,第 10秒 时锁就会强制过期,其他线程长驱直入,爆发并发事故。
✅ 正确姿势:
除非你非常确定业务能在指定时间内跑完,否则尽量不要传 leaseTime,让看门狗帮你自动续期。
💣 陷阱二:锁粒度太粗 —— 全服暂停键
❌ 错误姿势:
// 所有订单共用一把锁 RLock lock = redisson.getLock("LOCK_ORDER");⚠️ 后果:
这相当于把高速公路封成了独木桥。不管有多少个用户下单,同一时间只能处理一个。性能直接归零。
✅ 正确姿势:
锁的粒度越细越好。只锁那个具体产生竞争的资源 ID。
// 只锁这个订单 RLock lock = redisson.getLock("order:pay:" + orderId);💣 陷阱三:解锁的艺术 —— 谁加的锁谁来解
❌ 错误姿势:
try { // 业务逻辑 } finally { lock.unlock(); // 直接解锁 }⚠️ 后果:
- 如果业务执行超时,锁已经被自动释放了,你再去
unlock会抛出IllegalMonitorStateException。 - 如果不小心解了别人的锁(虽然 Redisson 有 ID 校验防止误删,但异常处理依然重要)。
✅ 正确姿势:
if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); }💣 陷阱四:重入锁的"递归噩梦"
Redisson 的锁虽然是可重入的(Reentrant),但如果你在递归或嵌套调用中不注意,很容易逻辑混乱。
❌ 风险代码:
void methodA() { lock.lock(); try { methodB(); // methodB 里又 lock 了一次 } finally { lock.unlock(); // 只解了一层 } }⚠️ 后果:
Redis 里的锁计数器(Counter)如果不归零,锁是不会释放的。确保你的加锁次数和解锁次数严格匹配。
💣 陷阱五:主从切换的"幽灵锁"
这是 Redis 架构天生的短板。
- Client A 在Master节点拿到了锁。
- Master 还没来得及把锁同步给 Slave,就宕机了。
- Slave 升级为新的 Master。
- Client B 来加锁,发现新 Master 上没锁,于是也加锁成功。
⚠️ 后果:
A 和 B 同时持有了锁。
解法:如果你不能容忍这个概率(极低),请看下文的 RedLock,或者转投 Zookeeper。对于 99% 的业务,我们选择接受这个风险。
五、RedLock 的爱恨情仇
有些面试官特别喜欢问 RedLock,但在实际工作中,它是一个让人爱恨交加的存在。
1. 它是为了解决什么?
解决 Redis 主从集群在 Failover(故障转移)时可能丢锁的问题。
2. 怎么用?
你需要准备3个或5个完全独立的 Redis 实例(不是 Cluster,不是 Sentinel,就是干干净净的单实例)。
RLock lock1 = redissonInstance1.getLock("lock"); RLock lock2 = redissonInstance2.getLock("lock"); RLock lock3 = redissonInstance3.getLock("lock"); // 创建红锁 RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3); try { // 同时向 3个 Redis 申请锁 // 只要有 > 1.5个 (即2个) 申请成功,就算赢 lock.lock(); // 业务逻辑 } finally { lock.unlock(); }