Redisson相关知识
RedissonFairLock公平锁中,list和zset的作用是什么?里面放的是什么?
好问题 👍,你问到 Redisson 的公平锁 (RedissonFairLock) 内部用到的 list
和 zset
,这是它跟普通 RLock
最大的区别点。
我来拆开讲 👇
1️⃣ 普通 RLock
的做法
-
RLock
用的是一个 hash 结构:- key: 锁名
- field: 线程标识(uuid + threadId)
- value: 重入次数
锁的续期靠看门狗,解锁时 hash 中计数减到 0 就删除。
👉 它 不保证公平性,只是 CAS 抢锁,谁快谁拿到。
2️⃣ 公平锁 RedissonFairLock
多了什么?
为了保证 FIFO 公平性,RedissonFairLock 引入了 两个额外的数据结构:
-
List (队列 list)
- 作用:保存 等待线程的队列顺序(谁先来,谁排在前面)
- 里面放的内容:每个线程的 唯一标识 (uuid:threadId)
- 类似一个排号系统,先进先出。
-
ZSet (有序集合 zset)
-
作用:保存 线程对应的允许获取锁的时间点(timestamp)
-
里面放的内容:
- member = 线程标识 (uuid:threadId)
- score = 可获取锁的时间戳(毫秒值)
-
👉 为什么要用 zset?
因为即使排在队列最前面的线程,也必须等前一个线程释放锁,才能根据时间戳判断“自己能不能获取锁”。
3️⃣ 执行流程
-
线程请求锁
- 加入 list,表示排队
- 加入 zset,标记自己最早能获取锁的时间
-
线程获取锁
- 检查自己是否在 list 队首
- 检查当前时间是否 ≥ 自己在 zset 里的可执行时间
- 满足条件 → 获取锁成功
- 否则继续阻塞等待
-
释放锁
- 删除自己在 list 和 zset 里的数据
- 通知下一个线程(队首 + 时间满足)去尝试获取锁
4️⃣ 对比总结
数据结构 | 存的内容 | 作用 |
---|---|---|
Hash (普通锁就有) | threadId -> 重入次数 | 记录当前持有锁的线程 |
List | 线程标识列表 [t1, t2, t3...] | 保证排队顺序(FIFO) |
ZSet | threadId -> timestamp | 控制线程最早能获取锁的时间 |
👉 list
保证 顺序,zset
保证 时机。
以及如果线程过期时间到了,还在执行怎么办?这里不能锁续约吗?
好问题 👍,你盯住了 Redisson 公平锁 (RedissonFairLock) 和普通锁的差别点,两个疑问都非常关键。
我分两部分解释 👇
1️⃣ 为什么要“等前一个线程释放锁 + 时间戳检查”
公平锁的目标:谁先来,谁先拿锁。
Redisson 的公平锁用 list + zset 来实现这个逻辑:
- list → 保证顺序,谁排在前面,谁先有资格尝试。
- zset → 保存“该线程可以获取锁的最早时间”。
流程:
-
线程 A 获取到锁,它的占有时间假设是
leaseTime = 10s
。 -
线程 B 来了,排进 list 队列,同时在 zset 里记录一个时间戳
now + 10s
。- 表示 只有 A 的锁过期/释放后,B 才能开始竞争。
-
当 A 释放锁时,B 才会被唤醒去检查:
- 自己是不是在队列头 (list 首元素)
- 当前时间 ≥ 自己在 zset 中的 score(可获取时间)
- 满足条件,才能真正拿到锁。
👉 这样避免了“后来的线程插队”,保证 FIFO 公平性。
如果没有时间戳,A 可能刚释放锁,B、C 一起去争,结果可能 C 抢到,就不公平了。
2️⃣ 如果线程 leaseTime 到了,还在执行怎么办?
这是一个非常现实的问题。
-
普通锁 (RLock):有 看门狗机制,会自动续期,保证执行中的线程不会因为 leaseTime 到期而丢锁。
-
公平锁 (FairLock):没有看门狗续期机制❌。
- 因为公平性需要依赖 时间戳 来判断“下一个线程能否获取锁”。
- 如果支持自动续期,可能会让后面的线程一直卡住,永远轮不到。
👉 所以在 Redisson 的公平锁里:
- 线程必须在 leaseTime 内完成任务。
- 如果没完成,锁会过期释放,后续线程就可能拿到锁,导致并发执行。
3️⃣ 总结
- “排在队首还要等时间戳” → 确保前一个线程释放锁之后,下一个线程按顺序、按时机才能拿到锁,避免插队。
- 公平锁没有自动续期 → 因为续期会破坏公平性。
- 如果任务耗时不确定,不推荐用公平锁,可以用普通
RLock
(有看门狗自动续期)。
那么,如果网络波动,唤醒B的包丢失了,时间在流逝,那么最终也会轮到C先抢到锁,B在队列中的信息会被自动清除吗?
你问到的这个场景很真实 👍:
如果 B 的唤醒包丢了,时间在流逝,最后是不是 C 会先拿到锁?那 B 在队列里的信息怎么办,会被自动清理吗?
我来详细解释 👇
1️⃣ B 的“唤醒丢失”可能吗?
在 RedissonFairLock 的实现里:
- 锁释放时,会用
publish
通知 下一个等待线程(而不是广播)。 - 这个通知依赖 Redis Pub/Sub。
- 如果 消息丢失(网络波动,或者 B 进程崩溃没处理到),B 的确可能错过抢锁的时机。
2️⃣ 时间流逝后会怎样?
假设:
- A leaseTime = 10s
- B 被安排的可抢锁时间 = now + 10s
- C 被安排的可抢锁时间 = now + 20s
如果 B 没有正常抢锁:
- 到了 20s,C 的 score 到期了。
- C 就会尝试去拿锁(条件是自己是 list 队首 + 时间满足)。
- 这时,Redis 的队列会把 B 从 list/zset 中清理掉,让 C 成为新的首元素。
3️⃣ B 的信息是如何清除的?
Redisson 在公平锁的实现里有 清理逻辑:
- 每次有线程尝试获取锁时,会检查队首元素对应的线程。
- 如果队首线程的等待时间已经过了,但它没去拿锁(比如 B 因为丢包挂住了),就会把它从 list 和 zset 中移除。
- 这样,后续线程(C)才能顺利继续。
👉 也就是说:
- B 不会永久卡在队首。
- 一旦 B 超时没动作,它会被移除,C 就能顶上。
4️⃣ 总结
- B 丢失唤醒信号后,确实可能错过抢锁机会。
- RedissonFairLock 通过 list + zset 清理机制 避免“僵尸节点”卡死队列。
- 最终 C 会顺利拿到锁,公平性依然能保证。
- 但对 B 来说,它就“饿死”了,需要重新发起加锁请求。