1. Redis 分布式锁的原子性保障

  • SET NX 命令:一个线程拿到锁,另一个线程拿不到锁就不能往下走,这是锁的互斥性。通过 SET key value NX EX expire 实现原子性加锁(只有 key 不存在时才设置),单命令天然原子性。
  • LUA 脚本:多步骤操作(如加锁+续期)需用 Lua 脚本保证原子性,避免命令间穿插导致的数据不一致。

2. 锁的自动释放与看门狗机制

  • 过期时间:锁必须设置过期时间,防止业务异常导致死锁。
  • 看门狗(Watchdog):业务执行期间定时续期锁(如每 10s 延长过期时间),避免锁提前失效。
  • 守护线程:看门狗设为守护线程,业务线程挂掉时JVM自动终止守护线程,防止锁永久占用。

3. 可重入锁的设计

  • Java的计数器机制:
    • synchronized 可重入性通过 对象头(Mark Word) 和 锁计数器 实现。
    • AQS 通过 状态变量(state) 和 当前持有线程 实现可重入性。
  • Redission的计数器机制:同一线程多次获取锁时,用 Redis 哈希结构(如 HINCRBY)要锁的东西为KEY,**线程id+UUID为FIELD,重入次数为VALUE,**记录重入次数,解锁时递减计数器,归零后释放锁。
    • 线程唯一标识:结合线程 ID + UUID 作为哈希字段,避免多线程冲突。

4. 可重入锁阻塞的实现

  • Java的方案自旋重试:未抢到锁的线程循环尝试加锁(如 while (true)),直到成功或超时。

    • synchronized
      • 轻量级锁阶段:线程在用户态通过 CAS 自旋短暂尝试获取锁,避免频繁切换到内核态。自旋次数 由 JVM 自适应调整,若自旋失败则锁升级为重量级锁。
      • 重量级锁阶段:未抢到锁的线程被放入 ObjectMonitor 的 _EntryList 队列,进入内核态阻塞。锁释放时,JVM 从队列中唤醒线程,但不保证唤醒顺序,允许新来的线程直接抢锁,体现非公平性。
    • AQS
      • 自旋 + 阻塞” 的混合策略。
  • Redission的方案订阅发布机制:

    • 如果线程未获取到锁,Redisson 会通过 Redis 的 Pub/Sub 订阅锁的释放事件。
    • 当锁被释放时(即其他线程释放锁),Redisson 会收到发布的通知。
    • 收到通知后,Redisson 会再次尝试获取锁。

    这种机制避免了传统自旋锁的高 CPU 消耗,同时提供了较高的性能和实时性


5. 主从架构下的锁丢失问题

  • 主从同步延迟:主节点写入锁后宕机,一个从节点转从为主,但从节点未同步导致新主节点无锁数据,其他线程可能重复加锁。
  • 解决方案:部署多主集群(如 Redis Cluster),加锁需所有主节点成功;或采用 RedLock 算法(半数以上节点成功)。

6. RedLock 算法的优缺点

要求REDIS要部署多个主节点,但是只需要半数以上加锁成功就行了,当某个线程加锁半数以上的节点成功了,那么其他线程就不可能再做到半数以上,加锁成功了,这就满足了互斥项

  • 优点:通过多个独立 Redis 节点实现跨节点锁,容忍单点故障。
  • 缺点:
    • 需严格时钟同步(否则可能误判锁有效性)。
    • Java 的 Stop-The-World GC 可能导致看门狗失效。
    • 实现复杂,运维成本高,实际应用较少。

7. Redisson 的优化方案

  • 底层实现:基于 Redis 哈希结构(非单纯 SET NX),支持可重入、公平锁等高级特性。
  • 多主集群:通过 Redis Cluster 部署,结合 RedLock 算法提升可靠性。

8. 锁的设计原则

  • 互斥性:同一时间仅一个客户端能持有锁。
  • 无饥饿:避免线程无限等待(如设置超时时间)。
  • 容错性:主从/多节点故障时仍能保证锁的有效性。

9. 常见陷阱

  • 线程 ID 冲突:集群环境下线程 ID 可能重复,需拼接 UUID 保证唯一性。
  • GC 暂停风险:JVM 的 Stop-The-World GC 可能导致看门狗失效,GC会暂停线程,导致看门狗无法对锁进行续期,然后锁就过期了

总结

Redis 分布式锁的核心是原子性 + 自动释放 + 容错机制,但实现细节需考虑主从同步、时钟偏差、GC 等复杂场景。

更多推荐