Redis 分布式锁与 Redisson:从抢券超卖讲到 WatchDog、可重入和 RedLock

分布式锁最适合用真实业务来理解。抢券场景:多个用户同时抢优惠券,系统要保证库存不能被扣成负数。

如果代码部署在单机上,synchronized 可能够用;但服务一旦集群部署到多个 Tomcat 实例,本地锁就只能锁住当前 JVM,挡不住其他节点上的线程。

这就是 Redis 分布式锁的使用场景:集群环境下,让多个服务实例争抢同一把锁。

一、为什么抢券会超卖?

普通抢券逻辑大概是:

java 复制代码
Integer num = redisTemplate.opsForValue().get("num");
if (num == null || num <= 0) {
    throw new RuntimeException("优惠券已抢完");
}
num = num - 1;
redisTemplate.opsForValue().set("num", num);

两个线程可能同时读到库存 1,然后都判断库存充足,最终都扣减成功。
Redis 库存 线程2 线程1 Redis 库存 线程2 线程1 查询库存 = 1 查询库存 = 1 扣减为 0 扣减为 0

问题不在 Redis,而在"查询库存"和"扣减库存"不是一个不可被打断的整体。

二、SETNX 实现分布式锁

Redis 实现分布式锁主要依赖 SETNX,也就是 SET if Not eXists:key 不存在时才能设置成功。

现在更推荐用一条 SET 命令同时完成加锁和设置过期时间:

text 复制代码
SET lock:coupon threadId NX EX 10

含义是:只有 lock:coupon 不存在时才设置成功,并且 10 秒后自动过期。
成功
失败
尝试获取锁
SET NX EX 是否成功
执行业务
释放锁
等待或重试

释放锁时也要小心,必须确认这把锁是自己的,再删除。否则线程 A 的锁过期后,线程 B 获取了新锁,线程 A 再执行删除就可能误删线程 B 的锁。

所以释放锁通常要用 Lua 脚本保证"判断锁标识 + 删除锁"是原子操作。

三、Redisson 做了哪些封装?

Redisson 是 Redis 分布式锁的成熟封装。课件里提到几个重点:

  1. 底层基于 SETNX 和 Lua 脚本保证原子性。
  2. 使用 WatchDog 给锁自动续期。
  3. 使用 Hash 结构记录线程标识和重入次数,实现可重入。
  4. 支持锁重试等待。

四、WatchDog:锁怎么自动续期?

如果业务执行时间超过锁过期时间,锁提前释放,就会有并发问题。Redisson 的 WatchDog 会在持有锁的线程还没执行完时自动续期。
Redis WatchDog 业务线程 Redis WatchDog 业务线程 加锁成功 定期检查锁仍被当前线程持有 续期锁过期时间 业务完成后释放锁

默认情况下,WatchDog 会周期性续期,避免业务还没执行完锁就过期。

五、可重入锁:为什么用 Hash?

可重入指的是同一个线程已经拿到锁后,可以再次获取同一把锁。

Redisson 会用 Hash 结构记录线程 id 和重入次数:

text 复制代码
heimalock
  thread-1 -> 2

同一个线程再次加锁时,重入次数加 1;释放锁时,重入次数减 1;只有减到 0 才真正删除锁。
线程第一次加锁
重入次数 = 1
线程再次加锁
重入次数 = 2
释放一次,重入次数 = 1
再次释放,删除锁

六、主从一致性和 RedLock

Redis 主从架构下,分布式锁还有一个风险:线程在 Master 上加锁成功,但锁还没同步到 Slave,Master 就宕机了。Slave 被提升为新 Master 后,其他线程可能又获取到同一把锁。

RedLock 的思路是:不要只在一个 Redis 实例上加锁,而是在多个独立 Redis 节点上加锁,只要超过 n / 2 + 1 个节点成功,就认为加锁成功。

但课件也提醒:RedLock 性能较差,不是默认推荐方案。如果业务真的要求极强一致,建议考虑 ZooKeeper 这类更适合强一致锁的方案。

七、面试回答模板

可以这样答:

我们项目里在抢券、幂等性或集群定时任务场景用过分布式锁。Redis 分布式锁底层主要依赖 SET NX EX 加锁,并用 Lua 脚本保证释放锁时判断和删除的原子性。实际项目里一般使用 Redisson,它封装了 WatchDog 自动续期、可重入锁、锁重试等能力。Redisson 的可重入基于 Hash 结构记录线程标识和重入次数。至于主从一致性问题,RedLock 可以缓解但性能较差,如果业务必须强一致,应该考虑 ZooKeeper。

相关推荐
Java 码思客9 小时前
【Redis分布式缓存实战】第3章 Redis核心机制深度解析
redis·分布式·缓存
小饼干在学嘎瓦10 小时前
秒杀场景Redis做预扣减,问题在哪里?
数据库·redis·mybatis
码不停蹄的玄黓10 小时前
生产可用的 Redis 分布式锁完整实现
数据库·redis·分布式
江湖中的阿龙10 小时前
Redis 五大核心数据类型底层原理
数据库·redis·缓存
Deep-w10 小时前
【MATLAB】微电网四DG逆变器下垂策略与分布式MPC协同控制仿真分析
开发语言·分布式·算法·matlab
周末也要写八哥10 小时前
项目简历:分布式Linux性能分析监控
分布式
AI人工智能+电脑小能手19 小时前
【大白话说Java面试题 第87题】【Mysql篇】第17题:分布式事务的实现原理?
java·数据库·分布式·mysql·面试
yurenpai(27届找实习中)1 天前
redis_点评(21.好友关注——关注、取关功能实现;共同关注功能实现)
数据库·redis·缓存
不爱编程的小陈1 天前
事务的进化:从MySQL单机事务到TiDB分布式事务的探究
分布式·mysql·tidb
Trouvaille ~1 天前
【Redis篇】Set 与 Zset:集合运算与排行榜的终极武器
数据库·redis·缓存·set·跳表·后端开发·zset