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。

相关推荐
李昊哲小课10 小时前
ubuntu26.04搭建kafka集群
分布式·kafka
snow@li11 小时前
Redis:数据库语法速查表 / 全景梳理
数据库·redis·缓存
Alluxio11 小时前
Alluxio + Anyscale Ray框架,实现跨区域训练数据读取速度20倍提升
人工智能·分布式·机器学习·缓存·ai
csdn2015_14 小时前
kafka如何保证消息的顺序
分布式·kafka
云技纵横17 小时前
Redis 延迟双删还是读到脏数据?一次缓存一致性事故
redis·后端·面试
csdn2015_17 小时前
kafka如何确保消息不丢失
数据库·分布式·kafka
9523618 小时前
RabbitMQ - 高级特性
java·spring boot·分布式·后端·rabbitmq
Database_Cool_18 小时前
阿里云 PolarDB-X vs TiDB 分布式数据库选型指南
数据库·分布式·阿里云·tidb
数据库小学妹18 小时前
分布式数据库架构怎么选?三种路线对比+迁移避坑指南
数据库·分布式·分布式数据库·数据库架构·数据迁移·数据库选型
大师兄666818 小时前
HarmonyOS7 分布式流转:手机到平板无缝接续案例
分布式·跨设备·harmonyos7·分布式流转