Redis 分布式锁原理:从 SET NX EX 到 Redisson 看门狗

一、加锁靠的是 SET NX EX 的原子性

Redis 给了个 setNX,名字拆开就是 set if not exists------只有 key 不存在才写进去。

具体加锁时,我们直接一条 set 命令搞定:

key 叫 lock,value 随便给,后面跟上 NX 表示不存在才设,再跟 EX 指定过期时间。

有人问,为啥非得塞进一条命令?分开先 setexpire 不行吗?

行是行,可那是两条命令,保证不了原子性

合到一条里执行,才保证要么全成、要么全不成。

二、过期时间不是可有可无,是防死锁的命门

加锁流程很简单:

线程来了先抢锁,返回 OK 就是抢到了,接着跑业务,跑完用 del 把 key 删掉,锁就释放了。

抢锁失败后,当前线程不会自动阻塞,需要业务代码或者 Redisson 自己实现等待和重试。

那不设置过期时间行不行?

想想极端情况:

你刚抢到锁、业务还没跑完,服务器突然宕机,锁没人来 del

这锁就永远挂着了,其他线程再也不可能拿到------死锁就这么来了。

所以必须给锁加失效时间。

哪怕中途宕机,到点自动过期,别的线程照样能进来。

三、锁时长失控,比死锁更隐蔽

过期时间兜底了死锁,可又引出新麻烦。

你加锁时给了个失效时间,万一业务执行时间超过了它,锁是不是自己就释放了?

可你的业务还没跑完啊。

这时候别的新线程一来,轻轻松松拿到锁。

分布式锁失效,导致多个线程同时进入临界区,业务逻辑的互斥性失效。

怎么治?

两条路:

第一,按业务耗时预估去设过期时间。

听着合理,实则悬------网络抖动、卡顿一来,时长就失控,根本压不准。

第二,给锁续期。

思路是另起一个线程盯着业务跑了多久,发现快超时了就把它持有的时长往上加。

这条才靠谱。

四、Redisson 的看门狗,把续期自动化了

自己起线程做监控太麻烦,市面上有现成方案------Redisson。

它底层基于 Redis 命令实现加锁,本质思想仍然是"只有不存在才能创建",但 Redisson 通过 Lua 脚本实现了更加复杂的锁逻辑。

加锁成功后,它会自动另开一个线程当看门狗(watchdog),专门盯着持锁线程,不断把锁的持有时间往长续。

Redisson 默认锁过期时间为30秒,看门狗会每隔约10秒检查一次,并把锁重新续期到30秒。

你手动释放锁时,会通知看门狗别再监听了------毕竟 key 都删了,没必要续。

五、抢不到锁?那就重试,别直接放弃

普通实现里,抢锁失败后通常直接返回失败,需要业务自己决定等待、重试或者退出。

Redisson 不一样,它更灵活。

它内部通过等待机制和重试逻辑不断尝试获取锁。

线程一要是很快就释放了,线程二几乎立刻就能拿到。

当然也不会无限循环。

它设了重试次数的阈值,超过就宣告获取失败。

业务本来也就几十毫秒,所以线程二通常等不了多久。

这层等待机制让高并发下锁的利用率高了不少,也就是所谓的重试机制

代码上先拿 RedissonClient,调 getLock 起一个锁,再调 tryLock 去抢。

tryLock 有两个常用版本:

  • tryLock(waitTime, unit):第一个参数是最大等待时长,比如 10 秒。
  • tryLock(waitTime, leaseTime, unit):多了中间的 leaseTime,即锁的失效时间。

这里有个坑要注意:

当 leaseTime 没有指定(默认 -1)时,Redisson 使用看门狗自动续期;

如果主动指定 leaseTime,则按照指定时间自动释放。

六、原子性真正的基石:Lua 脚本

Redisson 的核心锁操作(例如加锁、释放锁、判断锁归属)大量依赖 Lua 脚本保证原子性。

Lua 脚本最大的价值,就是能打包多条 Redis 命令一起执行,保证它们整体原子。

这点面试时别忘了提一嘴。

所以 Redisson 分布式锁的重点就三个:看门狗负责续期、抢不到的线程会重试等待、所有命令基于 Lua 保证原子。

七、可重入:同一线程能反复拿锁

面试官常追问:

这把锁能重入吗?

判断方法很简单:

看同一个线程能不能再次获取自己已经持有的锁。

比如:

java 复制代码
public void add1() {
    lock.lock();
    try {
        add2();
    } finally {
        lock.unlock();
    }
}

public void add2() {
    lock.lock();
    try {
        // 业务代码
    } finally {
        lock.unlock();
    }
}

线程执行 add1() 时已经持有 myLock,进入 add2() 后又尝试获取同一把锁。

如果第二次加锁成功,说明锁支持可重入;

如果第二次加锁阻塞,说明锁不可重入。

简单基于 SET NX EX 实现的 Redis 分布式锁,通常不支持重入,因为 Redis 只记录"锁存在",不知道是谁持有、持有了几次。

而 Redisson 会在 Redis 中记录线程标识和重入次数,因此同一个线程可以多次获取同一把锁。

八、主从一致性:Redlock 与 ZooKeeper 的取舍

企业里 Redis 通常搭主从集群:

一个主节点负责写(增删改),两个从节点负责读。

主节点写完要把数据同步给从节点。

问题就出在同步窗口上。

Java 应用建分布式锁是写操作,先落到主节点。

还没等同步,主节点宕机了。

哨兵机制从两个从里选一个新的主。

新线程直接打新主,因为旧数据没同步过来,它也能加锁成功。

这下两个线程同时持同一把锁,互斥性没了,业务在跑就容易出脏数据。

Redis 提出了 Redlock 算法,用于降低单节点故障导致锁失效的风险。

别只在一个实例上加锁,要在多个实例上加。

有个公式:N / 2 + 1,N 是节点总数。3 个节点情况下,需要超过半数,也就是至少 2 个节点加锁成功。

这样可以降低单节点故障导致锁失效的风险,提高分布式锁的可靠性。

但红锁毛病也不少,项目里其实很少用:

实现复杂、高并发下性能差、要维护多个独立 Redis 节点、运维繁琐,连 Redis 官方都不建议直接拿它解主从不一致。

如果业务更关注强一致性,可以考虑 ZooKeeper 等 CP 系统实现分布式锁。

九、面试怎么把这套讲完整

回到最开始的提问:Redis 分布式锁怎么实现的?

先讲业务场景,比如当时做了个抢券,才用上分布式锁。

再说项目里用的是 Redisson,底层 setNXLua 脚本,Lua 保证命令原子。

被追问"怎么控制锁时长",就抛看门狗:给持锁线程续期,默认每 10 秒续一次。

再被问"锁能重入吗",答能,靠线程 ID 判断,存储用哈希记线程标识和重入次数。

最后被追"主从一致能解决吗",答红锁能但很少用,真要强一致上 ZooKeeper。

这套答下来,核心信息基本就齐了。

相关推荐
Hammer_Hans2 小时前
DFT笔记98
java·开发语言·数据库
迪康Defender2 小时前
从静态存储到动态流转:终端透明加密两种模式实战解析
运维·服务器·网络·数据库·其他
明达智控技术2 小时前
国产PLC、远程IO模块在食品包装机的应用
分布式·自动化
2601_965798472 小时前
Is Piroll WordPress Theme Worth It for Freelancers? Full Review
数据库·php
冷凝娇3 小时前
【MySQL】2026总结(二)
数据库·mysql
万物皆字节4 小时前
【避坑】使用CacheEvict需要注意的坑
redis
aaa小葵4 小时前
LangGraph
数据库·人工智能
wWYy.4 小时前
Mysql:Read View
数据库·mysql
—Miss. Z—5 小时前
存储过程、用户定义函数、触发器和游标
数据库·mysql