从面试懵逼到通透掌握:分布式锁原理全解(附Redisson与Redlock机制剖析)

你是不是也有这样的经历?

简历上写着“精通 Java,精通 Redis,熟悉高并发场景”,结果一面下来,分布式锁怎么实现?Redisson是怎么加锁的?看门狗机制了解吗?锁丢失你知道怎么解决吗?全程“啊能能”,频频磕巴。

本文不整虚的,带你从0到1,一步步真正搞懂分布式锁的原理与落地实践,面试高频,架构核心,不能不会。


一、什么是“锁”?

本质很简单,锁的核心是互斥性

一个线程拿到了锁,其他线程就不能进入,必须等这个锁被释放后才能继续。

分布式锁本质也是为了实现跨进程、跨节点的互斥执行


二、最简单的分布式锁实现:SETNX 命令

使用 Redis 实现分布式锁的基本思路是:

1
SETNX lock_key thread_id

只要返回 1,说明加锁成功。否则说明其他线程已经获得锁,当前线程就拿不到。

1. 加锁后要能解锁

不能光会加锁,还要能安全地解锁。那是不是直接执行 DEL lock_key 就可以了?

⚠️ 错!这样会有误删他人锁的风险。

必须先判断锁是否是自己加的,再删除,需要两步操作,而 Redis 是单线程的,这两步操作如果分开执行就不是原子操作,可能导致严重问题。

2. 如何保证原子性?

使用 Lua 脚本,将判断+删除写成一个 Redis 命令,保证原子性。

1
2
3
4
5
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end

当然,有人会问:

“为什么 SETNX 命令就不用放进 Lua 脚本里?”

答:因为 SETNX 是单条 Redis 命令,Redis 天生是单线程,天然具备原子性。


三、线程挂了怎么办?锁自动释放机制

线程挂了没来得及释放锁,其他线程就永远拿不到锁了?

解决方案:给锁加一个过期时间

1
SET lock_key value NX PX 30000

加锁时指定过期时间(如 30 秒),即便线程挂了,锁也会自动释放。

但新问题来了:

业务还没执行完,锁就过期了。那其他线程就能拿到锁,造成并发执行了!


四、看门狗机制:保证业务执行期间锁不会过期

搞一个看门狗线程,定时续期:

  • 每隔一段时间(比如 10 秒)给锁续命;
  • 只要业务没执行完,锁就不会被 Redis 自动释放。

问题:业务线程挂了,看门狗还在续期怎么办?

设置看门狗线程为“守护线程”

守护线程的生命周期依赖主线程,业务线程一挂,看门狗自然终止。要守护的人都挂了,看门狗还有什么意义?


五、实现可重入锁

你写递归不?写方法调用不?

如果一个线程在方法 A 中获得了锁,又在方法 B 中再次尝试加锁,会不会失败?

如果你设计的锁不是可重入的,这会导致线程自己锁自己,造成死锁!

如何实现可重入锁?

对标 Java 的 synchronizedReentrantLock

  • 给每个锁维护一个锁计数器
  • 同一个线程加一次锁,计数器+1;
  • 释放锁时,计数器-1,减到0才真正释放锁。

Redis 实现方式:

Redisson 使用 Hash 结构来实现可重入:

  • key:锁的名称;
  • field:UUID + threadId,保证线程唯一;
  • value:重入次数(计数器)。
1
HSET lock_key uuid_threadId 1

为什么是 UUID + threadId?因为在分布式系统中 threadId 可能重复,需要拼接 UUID 保证唯一性。


六、实现阻塞锁(等待式加锁)

假设线程没抢到锁,是立即返回失败,还是等等再试?

实际开发中我们常常用阻塞锁:等待一会儿,再次尝试获取锁。

类比 Java 的 ReentrantLock

没抢到锁的线程会自旋(循环尝试获取),直到拿到锁。

分布式锁如何阻塞?

Redisson 使用了 Redis 的“发布/订阅”机制:

  1. 没抢到锁的线程订阅一个频道,阻塞等待;
  2. 拿到锁的线程执行完任务后发布消息;
  3. 被阻塞线程收到消息后再去抢锁;
  4. 超时机制控制,防止死等。

七、主从架构中的“锁丢失问题”

经典问题:

你在主节点执行了 SETNX,加锁成功,但此时主节点还没来得及同步到从节点就宕机了。

这时候 Redis 选了一个没有锁数据的从节点作为新主,其他线程再次加锁又成功了!

加锁明明成功,却又能被别人加锁 —— 锁丢了!


八、Redisson 的“联锁”机制解决锁丢失问题

Redisson 支持“联锁”(MultiLock):

部署多个 Redis 主节点(多主或主从),每次加锁都要对所有主节点加锁成功,才算真正加锁成功。

即便其中某个 Redis 宕机,其它节点仍保有锁数据,仍能保障互斥性。

但这带来了新问题:

  • 某个 Redis 延迟大或挂了,加锁失败;
  • 加锁要回滚其他 Redis 节点的数据,开销大;
  • 性能下降,失败率高。

九、红锁(RedLock):性能与一致性的折中方案

Redis 官方提出了红锁算法,改进联锁的缺点:

只需要半数以上节点加锁成功就认为锁加成功。

为什么是“半数以上”?这涉及分布式共识协议中的多数派原则

  • 如果线程 A 已经拿到了多数节点的锁;
  • 线程 B 无法同时拿到多数节点的锁;
  • 互斥性仍然能保证。

红锁特点:

  • 时间窗口严格要求,必须在一定时间内完成加锁;
  • 可容忍个别节点挂掉;
  • 性能比全联锁高,但一致性仍有保证。

十、红锁也不是万能的

Redlock 理论上看起来很美,但实际落地仍存在问题:

  1. 时钟不一致:多个 Redis 节点可能时间不统一,导致锁判断误差;
  2. JVM 暂停问题(STW):Java 有 GC 暂停,可能造成续期失败,锁被释放;
  3. 部署复杂:红锁需要多个主节点,部署维护成本高;
  4. 实现难度大:多数公司不愿承担额外运维与风险。

所以红锁虽然可靠,但在工业界用得非常少


总结:真正掌握分布式锁的你,必须知道这些

能力点 内容
锁基本原理 互斥、释放、原子性
基础命令 SETNX、EX/PX、Lua 脚本
看门狗 守护线程、续期机制
可重入 Hash结构+计数器、UUID+ThreadId
阻塞锁 发布/订阅、自旋、超时控制
主从问题 锁丢失、联锁、多主
红锁机制 多主、多数派、时间窗口
局限性 时钟漂移、GC暂停、部署复杂