【深度复盘】Redis 分布式锁:从 SETNX 到 Redisson 看门狗的架构权衡

【深度复盘】Redis 分布式锁:从 SETNX 到 Redisson 看门狗的架构权衡

标签: Redis 分布式锁 Redisson 并发编程 架构设计
复盘时间: 2026-01-25

一、 为什么需要分布式锁?(演进之路)

在单机多线程环境下,我们使用 synchronizedReentrantLock;但在微服务/集群环境下,本地锁无法锁住其他 JVM 进程,因此需要引入第三方组件(Redis/Zookeeper)来实现全局锁。

1. 原始阶段:SETNX

  • 命令: SETNX lock:key value (Set if Not Exists)
  • 致命缺陷: 如果机器宕机或程序崩溃,锁未释放(死锁)。
  • 补丁: 引入过期时间(TTL)。

2. 进阶阶段:原子性与误删

  • 原子性问题: SETNXEXPIRE 是两步操作,非原子。
    • 解决: 使用 Redis 2.6+ 复合命令:SET key value EX 10 NX
  • 误删问题: 线程 A 卡顿,锁过期自动释放;线程 B 上锁;线程 A 醒来执行 DEL,把线程 B 的锁删了。
    • 解决: UUID + Lua 脚本
    • Value 存入唯一 ID(如 UUID),删除前判断 if (get(key) == uuid) { del(key) },必须通过 Lua 保证判断和删除的原子性。

3. 终极阶段:续期问题

  • 痛点: 业务没跑完,锁过期了怎么办?
  • 解决: 引入 守护线程(Daemon Thread) 自动续期。这就是 Redisson 的核心价值。

二、 Redisson 核心机制深度解析

1. 看门狗 (WatchDog) 机制

  • 触发条件: 加锁时不指定 LeaseTime(租约时间)。
  • 原理:
    • 默认租约时间 (lockWatchdogTimeout) 为 30秒
    • Redisson 启动一个后台线程,每隔 10秒 (1/3 租约时间) 检查一次,如果主线程还持有锁,就重置过期时间为 30秒。
  • 停止条件: 显式解锁 (unlock) 或 客户端宕机(看门狗线程随之死亡,30秒后锁自动过期)。

2. 架构哲学:Safety vs Liveness(面试杀手锏)

问题: 如果业务代码死循环,看门狗一直续期导致锁无法释放(死锁),是不是不如"固定过期时间"好?

架构决策:

  • 场景 A:固定过期时间(无看门狗)
    • 业务死循环 -> 锁过期释放 -> 其他线程进入 -> 并发修改同一数据。
    • 后果: 脏写 (Dirty Write) 。数据被污染,账平不上了。(Safety 丢失)
  • 场景 B:看门狗自动续期
    • 业务死循环 -> 锁一直存在 -> 其他线程拿不到锁(阻塞)。
    • 后果: 服务停摆 (Deadlock) 。业务卡住,但数据是安全的。(Liveness 丢失)

结论: 对于金融/交易等核心业务,数据一致性(Safety)高于可用性(Liveness) 。宁可人工介入重启服务解决死锁,也不能容忍数据被脏写。因此,看门狗机制是核心业务的"保护神"

3. 锁等待机制:Pub/Sub vs 自旋

  • SETNX: 失败返回 0,客户端通常需要 while(true) 自旋重试,消耗 CPU。
  • Redisson: 利用 Redis 的 Pub/Sub(发布订阅)
    • 获取锁失败时,订阅锁的释放频道,然后挂起当前线程(Semaphore)。
    • 收到释放消息后,唤醒线程再次尝试。
    • 优势: 无效等待时不耗 CPU,性能更优。

三、 Redisson API 实战指南

在代码中,根据业务场景选择不同的锁策略:

1. 死磕模式(核心业务)

适用于必须执行且不能并发的任务(如每日跑批、资金结算)。

java 复制代码
RLock lock = redisson.getLock("myLock");
lock.lock(); // 激活看门狗,默认30s,自动续期
try {
    // 业务逻辑(哪怕跑1小时也不怕)
} finally {
    lock.unlock();
}

2. 优雅等待模式(高并发 Web 业务)

适用于大多数互联网请求,防止请求堆积。

java 复制代码
// 尝试等待10秒,拿到锁后,锁在30秒后强制过期(无看门狗!)
// 参数:waitTime, leaseTime, unit
boolean res = lock.tryLock(10, 30, TimeUnit.SECONDS); 
if (res) {
   try {
     // 业务处理
   } finally {
     lock.unlock();
   }
} else {
   throw new RuntimeException("系统繁忙,请稍后再试"); // 快速失败
}

注意: 一旦指定了 leaseTime(第二个参数),看门狗机制失效

3. 快速失败模式(防抖/非核心)

适用于防止用户重复点击。

java 复制代码
if (lock.tryLock()) { // 不等待,拿不到立刻返回 false
    try { ... } finally { lock.unlock(); }
} else {
    return;
}

四、 总结(记忆钩子)

  1. 原子性: 加锁用 SET ... NX EX,解锁用 Lua 脚本。
  2. 误删: Value 必须存 UUID 做身份验证。
  3. 看门狗: 解决"业务没跑完锁过期"的问题。不传 leaseTime 时触发。
  4. 取舍: 核心业务选看门狗(保数据),高并发业务选固定过期时间(保服务)。
相关推荐
想做后端的前端2 小时前
Redis中的Lua使用
数据库·redis·lua
【赫兹威客】浩哥2 小时前
【赫兹威客】完全分布式Flink测试教程
大数据·分布式·flink
csgo打的菜又爱玩2 小时前
数仓整体架构和建模架构
java·大数据·开发语言·架构
予枫的编程笔记2 小时前
【Redis实战进阶篇1】Redis 分布式锁:从手写实现到 Redisson 最佳实践
redis·分布式·wpf
小程故事多_802 小时前
AI Agent架构革命,Skills模式为何能颠覆传统Workflow?
人工智能·架构·aigc
瑶光守护者2 小时前
【Rockchip RK3576】边缘计算与 AIoT 领域的全能架构深度解析
人工智能·架构·边缘计算
瑶山2 小时前
Spring Cloud微服务搭建二、分布式定时任务Quartz+MySQL接入
分布式·mysql·spring cloud·微服务·quartz
小北方城市网2 小时前
Spring Cloud Gateway 生产问题排查与性能调优全攻略
redis·分布式·缓存·性能优化·mybatis
●VON2 小时前
无状态 Widget 下的实时排序:Flutter for OpenHarmony 中 TodoList 的排序策略与数据流控制
学习·flutter·架构·交互·openharmony·von