分布式锁-redission锁重试和WatchDog机制

一、前言:为什么你的分布式锁"有时失效"?

你是否遇到过这些问题:

  • 业务执行了 40 秒,但锁 30 秒就没了,导致超卖?
  • 多个服务同时抢锁,有的直接失败,没有重试?
  • 明明加了锁,却出现并发修改?

根本原因,很可能在于你没用好 Redisson 的两大核心机制

🔹 锁重试(Lock Retry) ------ 智能等待,避免立即失败

🔹 WatchDog(看门狗) ------ 自动续期,防止业务超时丢锁

本文将带你彻底掌握这两大机制的工作原理、使用方式与避坑指南


二、机制一:锁重试(Lock Retry)------ 智能等待,不轻易放弃

场景问题:

当多个线程/服务同时请求同一把锁时,未获得锁的一方该如何处理

  • 直接报错? → 用户体验差
  • 无限等待? → 线程阻塞,资源耗尽

Redisson 的解决方案:

提供 带超时的 tryLock() ,支持最大等待时间 + 持有时间

API 示例:
java 复制代码
RLock lock = redisson.getLock("inventory:lock");

// 最多等待 3 秒获取锁,获得后最多持有 10 秒
boolean isLocked = lock.tryLock(3, 10, TimeUnit.SECONDS);

if (isLocked) {
    try {
        // 扣减库存等业务逻辑
        deductStock();
    } finally {
        lock.unlock();
    }
} else {
    throw new BusinessException("系统繁忙,请稍后再试");
}

🔍 底层原理:

  1. 客户端尝试加锁(执行 Lua 脚本)
  2. 若失败,订阅 Redis 的解锁频道(Pub/Sub)
  3. 启动定时任务,每隔 1/3 持有时间(如 10s/3 ≈ 3.3s)重试
  4. 一旦持有者释放锁,会发布消息,唤醒所有等待者竞争

✅ 优势:避免轮询浪费 CPU,实现高效等待


三、机制二:WatchDog(看门狗)------ 自动续期,永不丢锁

经典问题重现:

java 复制代码
lock.lock(); // 默认 TTL = 30 秒
Thread.sleep(40_000); // 业务耗时 40 秒
// 此时锁已过期!其他线程可进入 → 并发安全破坏!

WatchDog 如何解决?

只要你不 unlock,锁就永远不会过期!

工作流程:
  1. 调用无参 lock() 时,不设置 TTL
  2. 同时启动后台线程(WatchDog)
  3. 每隔 10 秒internalLockLeaseTime / 3 = 30000 / 3)检查:
    • 如果当前线程仍持有该锁 → 执行 PEXPIRE myLock 30000
  4. 调用 unlock() 后,取消 WatchDog 定时任务

📊 时间线示例:

复制代码
t=0s   → 加锁,启动 WatchDog
t=10s  → WatchDog 续期(TTL 重置为 30s)
t=20s  → 再次续期
t=25s  → unlock() 被调用
t=25s+ → WatchDog 停止,锁被删除

💡 关键点 :WatchDog 只在 无参 lock() 时启用!

若使用 lock(10, SECONDS)不会启动 WatchDog,10 秒后强制过期!


四、源码级解析:WatchDog 如何实现?

核心类:org.redisson.RedissonLock

1. 加锁时注册续期任务:
java 复制代码
void scheduleExpirationRenewal(...) {
    // 将锁信息存入本地 Map
    ExpirationEntry entry = new ExpirationEntry();
    expirationRenewalMap.put(lockName, entry);
    
    // 启动定时任务
    Timeout task = commandExecutor.getConnectionManager().newTimeout(
        timeout -> {
            // 发送 PEXPIRE 命令续期
            renewExpirationAsync(threadId);
            // 递归调度下一次
            scheduleExpirationRenewal(...);
        },
        internalLockLeaseTime / 3, // 默认 10 秒
        TimeUnit.MILLISECONDS
    );
    entry.addTimeout(task);
}
2. 解锁时取消任务:
java 复制代码
void cancelExpirationRenewal(...) {
    ExpirationEntry entry = expirationRenewalMap.remove(lockName);
    if (entry != null) {
        for (Timeout timeout : entry.getTimeouts()) {
            timeout.cancel(); // 取消所有定时任务
        }
    }
}

设计精妙:通过本地 Map + Netty Timeout 实现高效管理


五、锁重试 + WatchDog 联合使用示例

java 复制代码
public void seckill(Long productId, Long userId) {
    RLock lock = redisson.getLock("seckill:product:" + productId);
    
    try {
        // 最多等待 2 秒抢锁,抢到后由 WatchDog 自动续期
        if (lock.tryLock(2, TimeUnit.SECONDS)) {
            // 业务可能耗时较长(如调用风控、支付)
            doSeckillBusiness(productId, userId);
        } else {
            log.warn("用户 {} 抢购商品 {} 失败:锁等待超时", userId, productId);
            throw new BusinessException("活动太火爆,请稍后再试");
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new RuntimeException(e);
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock(); // 安全解锁
        }
    }
}

最佳实践

  • 抢锁阶段 :用 tryLock(waitTime) 避免无限阻塞
  • 持有阶段:依赖 WatchDog 自动续期,无需担心业务超时

六、常见误区与避坑指南

❌ 误区 1:认为 lock(30, SECONDS) 会自动续期

真相只有无参 lock() 才启动 WatchDog
lock(30, SECONDS) 表示"最多持有 30 秒",到期强制释放。

❌ 误区 2:在异步线程中 unlock()

java 复制代码
lock.lock();
CompletableFuture.runAsync(() -> {
    lock.unlock(); // ❌ threadId 不匹配,无法解锁!
});

正解 :解锁必须在加锁的同一线程中进行

❌ 误区 3:忘记判断是否持有锁就 unlock()

风险 :可能抛出异常或误删他人锁
建议 :使用 lock.isHeldByCurrentThread() 判断


七、性能与可靠性对比

方案 是否支持重试 是否自动续期 适用场景
手写 Redis SET NX 简单低并发
Redisson lock() ✅(阻塞) 通用高可靠
Redisson tryLock(w, h) ✅(超时) ✅(仅无参 holdTime) 用户交互型业务

📌 推荐

  • 后台任务 → 用 lock()
  • Web 请求 → 用 tryLock(waitTime, leaseTime)

八、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
小江的记录本1 天前
【微服务与云原生架构】DevOps、CI/CD流水线、GitOps 系统性知识体系
分布式·后端·ci/cd·微服务·云原生·架构·devops
2603_954708311 天前
微电网混合控制架构:主从与对等控制的优势融合
分布式·安全·架构·能源·需求分析
zhangzeyuaaa1 天前
Python多进程同步与共享内存完全指南:从Lock到分布式共享
开发语言·分布式·python
aini_lovee1 天前
多智能体点对点转换的分布式模型预测控制(DMPC)
分布式
_F_y1 天前
仿RabbitMQ实现消息队列-项目设计
分布式·rabbitmq
keep intensify2 天前
MIT 6.824 lab3B/C
分布式·后端·golang
java1234_小锋2 天前
RabbitMQ中有哪几种交换机类型?
分布式·rabbitmq
代码漫谈2 天前
探索RabbitMQ集群:如何实现消息的高可用性和负载均衡
分布式·消息队列·rabbitmq·负载均衡
weisian1512 天前
Java并发编程--45-分布式一致性协议入门:Raft、Paxos与ZAB的核心思想
java·分布式·raft·paxos·zab
juniperhan2 天前
Flink 系列第17篇:Flink Table&SQL 核心概念、原理与实战详解
大数据·数据仓库·分布式·sql·flink