【深度复盘】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. 取舍: 核心业务选看门狗(保数据),高并发业务选固定过期时间(保服务)。
相关推荐
一叶飘零_sweeeet2 小时前
分布式权限体系破局:统一认证授权与 OAuth2.0 全链路架构落地实战
分布式·架构
WmStack2 小时前
‘秒杀’功能实现
redis
智算菩萨2 小时前
【How Far Are We From AGI】4 AGI的“生理系统“——从算法架构到算力基座的工程革命
论文阅读·人工智能·深度学习·算法·ai·架构·agi
无籽西瓜a2 小时前
Docker 环境下 Redis Lua 脚本部署与执行
redis·docker·lua
疯狂成瘾者2 小时前
Redis 实用学习清单
redis·学习
七夜zippoe2 小时前
消息队列选型:Kafka vs RabbitMQ vs Redis 深度对比
redis·python·kafka·消息队列·rabbitmq
乾元2 小时前
全球治理: 从《AI 法案》看安全合规的国际趋势
网络·人工智能·安全·机器学习·网络安全·架构·安全架构
阴暗扭曲实习生2 小时前
135编辑器素材管理系统的技术架构
架构·编辑器
前端不太难3 小时前
如何设计 AI Native 鸿蒙应用架构
人工智能·架构·harmonyos
iMingzhen3 小时前
不想引入 Redis,我用一张 SQLite 表实现了消息队列
数据库·redis·ai·sqlite