一、加锁靠的是 SET NX EX 的原子性
Redis 给了个 setNX,名字拆开就是 set if not exists------只有 key 不存在才写进去。
具体加锁时,我们直接一条 set 命令搞定:
key 叫 lock,value 随便给,后面跟上 NX 表示不存在才设,再跟 EX 指定过期时间。
有人问,为啥非得塞进一条命令?分开先 set 再 expire 不行吗?
行是行,可那是两条命令,保证不了原子性。
合到一条里执行,才保证要么全成、要么全不成。

二、过期时间不是可有可无,是防死锁的命门
加锁流程很简单:
线程来了先抢锁,返回 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,底层 setNX 加 Lua 脚本,Lua 保证命令原子。
被追问"怎么控制锁时长",就抛看门狗:给持锁线程续期,默认每 10 秒续一次。
再被问"锁能重入吗",答能,靠线程 ID 判断,存储用哈希记线程标识和重入次数。
最后被追"主从一致能解决吗",答红锁能但很少用,真要强一致上 ZooKeeper。
这套答下来,核心信息基本就齐了。