Redisson相关知识

RedissonFairLock公平锁中,list和zset的作用是什么?里面放的是什么?

好问题 👍,你问到 Redisson 的公平锁 (RedissonFairLock) 内部用到的 listzset,这是它跟普通 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️⃣ 执行流程

  • 线程请求锁

    1. 加入 list,表示排队
    2. 加入 zset,标记自己最早能获取锁的时间
  • 线程获取锁

    1. 检查自己是否在 list 队首
    2. 检查当前时间是否 ≥ 自己在 zset 里的可执行时间
    3. 满足条件 → 获取锁成功
    4. 否则继续阻塞等待
  • 释放锁

    1. 删除自己在 list 和 zset 里的数据
    2. 通知下一个线程(队首 + 时间满足)去尝试获取锁

4️⃣ 对比总结

数据结构 存的内容 作用
Hash (普通锁就有) threadId -> 重入次数 记录当前持有锁的线程
List 线程标识列表 [t1, t2, t3...] 保证排队顺序(FIFO)
ZSet threadId -> timestamp 控制线程最早能获取锁的时间

👉 list 保证 顺序zset 保证 时机

以及如果线程过期时间到了,还在执行怎么办?这里不能锁续约吗?

好问题 👍,你盯住了 Redisson 公平锁 (RedissonFairLock) 和普通锁的差别点,两个疑问都非常关键。

我分两部分解释 👇


1️⃣ 为什么要"等前一个线程释放锁 + 时间戳检查"

公平锁的目标:谁先来,谁先拿锁

Redisson 的公平锁用 list + zset 来实现这个逻辑:

  • list → 保证顺序,谁排在前面,谁先有资格尝试。
  • zset → 保存"该线程可以获取锁的最早时间"。

流程:

  1. 线程 A 获取到锁,它的占有时间假设是 leaseTime = 10s

  2. 线程 B 来了,排进 list 队列,同时在 zset 里记录一个时间戳 now + 10s

    • 表示 只有 A 的锁过期/释放后,B 才能开始竞争
  3. 当 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 来说,它就"饿死"了,需要重新发起加锁请求。