从面试懵逼到通透掌握:分布式锁原理全解(附Redisson与Redlock机制剖)
从面试懵逼到通透掌握:分布式锁原理全解(附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 | if redis.call("get",KEYS[1]) == ARGV[1] then |
当然,有人会问:
“为什么 SETNX 命令就不用放进 Lua 脚本里?”
答:因为 SETNX
是单条 Redis 命令,Redis 天生是单线程,天然具备原子性。
三、线程挂了怎么办?锁自动释放机制
线程挂了没来得及释放锁,其他线程就永远拿不到锁了?
解决方案:给锁加一个过期时间!
1 | SET lock_key value NX PX 30000 |
加锁时指定过期时间(如 30 秒),即便线程挂了,锁也会自动释放。
但新问题来了:
业务还没执行完,锁就过期了。那其他线程就能拿到锁,造成并发执行了!
四、看门狗机制:保证业务执行期间锁不会过期
搞一个看门狗线程,定时续期:
- 每隔一段时间(比如 10 秒)给锁续命;
- 只要业务没执行完,锁就不会被 Redis 自动释放。
问题:业务线程挂了,看门狗还在续期怎么办?
设置看门狗线程为“守护线程”!
守护线程的生命周期依赖主线程,业务线程一挂,看门狗自然终止。要守护的人都挂了,看门狗还有什么意义?
五、实现可重入锁
你写递归不?写方法调用不?
如果一个线程在方法 A 中获得了锁,又在方法 B 中再次尝试加锁,会不会失败?
如果你设计的锁不是可重入的,这会导致线程自己锁自己,造成死锁!
如何实现可重入锁?
对标 Java 的 synchronized
和 ReentrantLock
:
- 给每个锁维护一个锁计数器;
- 同一个线程加一次锁,计数器+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 的“发布/订阅”机制:
- 没抢到锁的线程订阅一个频道,阻塞等待;
- 拿到锁的线程执行完任务后发布消息;
- 被阻塞线程收到消息后再去抢锁;
- 超时机制控制,防止死等。
七、主从架构中的“锁丢失问题”
经典问题:
你在主节点执行了 SETNX
,加锁成功,但此时主节点还没来得及同步到从节点就宕机了。
这时候 Redis 选了一个没有锁数据的从节点作为新主,其他线程再次加锁又成功了!
加锁明明成功,却又能被别人加锁 —— 锁丢了!
八、Redisson 的“联锁”机制解决锁丢失问题
Redisson 支持“联锁”(MultiLock):
部署多个 Redis 主节点(多主或主从),每次加锁都要对所有主节点加锁成功,才算真正加锁成功。
即便其中某个 Redis 宕机,其它节点仍保有锁数据,仍能保障互斥性。
但这带来了新问题:
- 某个 Redis 延迟大或挂了,加锁失败;
- 加锁要回滚其他 Redis 节点的数据,开销大;
- 性能下降,失败率高。
九、红锁(RedLock):性能与一致性的折中方案
Redis 官方提出了红锁算法,改进联锁的缺点:
只需要半数以上节点加锁成功就认为锁加成功。
为什么是“半数以上”?这涉及分布式共识协议中的多数派原则:
- 如果线程 A 已经拿到了多数节点的锁;
- 线程 B 无法同时拿到多数节点的锁;
- 互斥性仍然能保证。
红锁特点:
- 时间窗口严格要求,必须在一定时间内完成加锁;
- 可容忍个别节点挂掉;
- 性能比全联锁高,但一致性仍有保证。
十、红锁也不是万能的
Redlock 理论上看起来很美,但实际落地仍存在问题:
- 时钟不一致:多个 Redis 节点可能时间不统一,导致锁判断误差;
- JVM 暂停问题(STW):Java 有 GC 暂停,可能造成续期失败,锁被释放;
- 部署复杂:红锁需要多个主节点,部署维护成本高;
- 实现难度大:多数公司不愿承担额外运维与风险。
所以红锁虽然可靠,但在工业界用得非常少。
总结:真正掌握分布式锁的你,必须知道这些
能力点 | 内容 |
---|---|
锁基本原理 | 互斥、释放、原子性 |
基础命令 | SETNX、EX/PX、Lua 脚本 |
看门狗 | 守护线程、续期机制 |
可重入 | Hash结构+计数器、UUID+ThreadId |
阻塞锁 | 发布/订阅、自旋、超时控制 |
主从问题 | 锁丢失、联锁、多主 |
红锁机制 | 多主、多数派、时间窗口 |
局限性 | 时钟漂移、GC暂停、部署复杂 |