分布式锁王者之路:Redisson分布式锁原理完全解密

发布时间:2026/5/31 19:06:28

分布式锁王者之路:Redisson分布式锁原理完全解密 分布式锁王者之路Redisson分布式锁原理完全解密前言一、分布式锁的核心诉求二、Redisson 锁的核心数据结构三、完整加锁流程图四、加锁核心源码剖析第一步入口方法 lock()第二步核心加锁逻辑 lock()第三步核心 Lua 脚本加锁的原子性保证五、Watch Dog自动续期机制问题背景Watch Dog 解决方案什么时候 Watch Dog 不启动六、锁释放流程七、可重入原理解析八、锁的阻塞重试机制九、其他分布式锁类型简介1. 公平锁FairLock2. 读写锁ReadWriteLock3. 联锁MultiLock4. 红锁RedLock十、常见面试题汇总Q1Redisson 锁和 SETNX 手动实现的锁有什么区别Q2Watch Dog 在多长时间续期一次Q3如果业务执行时间超过 30 秒但 Watch Dog 没启动会怎样Q4Redisson 锁是强一致性的吗Q5Lua 脚本为什么能保证原子性总结)The Begin点点关注收藏不迷路⬇ ⬇ 底部 ⬇ ⬇前言在分布式系统中分布式锁是最基础的并发控制工具。而提到 Java 生态中的分布式锁Redisson 几乎是绕不开的名字。相比手动基于 Redissetnx实现的简单锁Redisson 提供了可重入性同一线程可多次获取同一把锁自动续期Watch Dog 机制防止业务未完成锁就过期阻塞重试获取锁失败时可等待并自动重试多种锁模式公平锁、读写锁、联锁、红锁等今天这篇文章我们从数据结构 → 加锁流程 → Lua 脚本 → Watch Dog → 可重入原理层层深入彻底搞懂 Redisson 分布式锁的底层实现。一、分布式锁的核心诉求在深入源码之前我们先明确一个好的分布式锁必须满足什么特性说明互斥性同一时刻只有一个客户端能持有锁防死锁锁有自动过期机制避免客户端宕机导致死锁可重入同一线程可多次获取同一把锁避免死锁高可用Redis 集群部署锁服务不依赖单点阻塞重试获取锁失败时可等待唤醒而非立即失败Redisson 的分布式锁恰恰在这几个方面都做了精密的设计。二、Redisson 锁的核心数据结构Redisson 的普通可重入锁底层基于 Redis 的Hash 数据结构Key: myLock # 锁的名称 Field: uuid:threadId # 持有锁的线程标识 Value: 2 # 重入次数结构示意图Redis Hash ----------------------------------- | Key: anyLock | ----------------------------------- | Field | Value | | b31d8b2a-0e9c-4d93-989b-234d | 1 | | (客户端ID:线程ID) | 重入次数 | -----------------------------------关键点Key锁的唯一标识如order:123:lockField持有锁的客户端 ID 线程 IDValue重入次数每次重入 1释放时 -1三、完整加锁流程图开始加锁 (tryLock) │ ▼ ┌─────────────────────────────────────────────────────────┐ │ 执行 Lua 脚本 │ │ 1. exists KEYS[1] ? → 否 → 创建 Hash设置过期时间 │ │ 2. hexists KEYS[1] ARGV[2] ? → 是 → 重入次数1 │ │ 3. 其他 → 返回锁的剩余过期时间 (pttl) │ └─────────────────────────────────────────────────────────┘ │ ┌───────────────┴───────────────┐ │ │ 返回 nil 返回 ttl (0) (加锁成功) (其他线程持有锁) │ │ ▼ ▼ 执行成功 订阅解锁通知 │ │ ▼ ▼ (若 leaseTime-1) 进入循环等待 启动 Watch Dog 收到通知后重试 │ │ ▼ ▼ 执行业务 再次尝试加锁 │ ▼ 释放锁 (unlock)四、加锁核心源码剖析第一步入口方法lock()// RedissonLock.javapublicvoidlock(){try{lock(-1,null,false);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}第二步核心加锁逻辑lock()privatevoidlock(longleaseTime,TimeUnitunit,booleaninterruptibly)throwsInterruptedException{longthreadIdThread.currentThread().getId();// 关键步骤1尝试获取锁LongttltryAcquire(leaseTime,unit,threadId);// ttl null 表示获取锁成功if(ttlnull){return;}// 关键步骤2获取锁失败订阅锁释放通知RFutureRedissonLockEntrysubscribeFuturesubscribe(threadId);// ... 等待订阅完成try{while(true){// 再次尝试获取锁ttltryAcquire(leaseTime,unit,threadId);if(ttlnull){break;// 获取成功}// 等待锁释放通知阻塞if(ttl0){await(ttl,TimeUnit.MILLISECONDS);}}}finally{unsubscribe(subscribeFuture,threadId);}}第三步核心 Lua 脚本加锁的原子性保证这是 Redisson 最核心的代码所有加锁逻辑都在一个 Lua 脚本中完成保证原子性-- KEYS[1]锁的名称-- ARGV[1]锁的过期时间毫秒-- ARGV[2]线程标识UUID:ThreadId-- 1. 锁不存在 → 创建锁if(redis.call(exists,KEYS[1])0)thenredis.call(hincrby,KEYS[1],ARGV[2],1);redis.call(pexpire,KEYS[1],ARGV[1]);returnnil;-- 返回 nil 表示加锁成功end;-- 2. 锁存在且是当前线程 → 重入次数 1if(redis.call(hexists,KEYS[1],ARGV[2])1)thenredis.call(hincrby,KEYS[1],ARGV[2],1);redis.call(pexpire,KEYS[1],ARGV[1]);returnnil;-- 加锁成功end;-- 3. 其他线程持有锁 → 返回锁的剩余过期时间returnredis.call(pttl,KEYS[1]);为什么用 Lua 脚本多条 Redis 命令在脚本中原子执行避免了“判断锁存在 → 加锁”两个操作的并发问题五、Watch Dog自动续期机制这是 Redisson 最亮眼的设计之一。问题背景如果锁设置了 30 秒自动过期但业务执行需要 45 秒锁就会提前释放导致其他线程拿到锁引发并发问题。Watch Dog 解决方案当leaseTime -1未指定锁超时时间时Redisson 会默认锁过期时间30 秒LockWatchdogTimeout启动定时任务每10 秒30/3执行一次自动续期如果线程仍持有锁重置过期时间为 30 秒// 源码简化版privatevoidscheduleExpirationRenewal(longthreadId){// 创建定时任务TimeouttaskcommandExecutor.getConnectionManager().newTimeout(newTimerTask(){Overridepublicvoidrun(Timeouttimeout){// Lua 脚本如果锁还在重置过期时间renewExpiration();// 递归调度继续续期scheduleExpirationRenewal(threadId);}},internalLockLeaseTime/3,TimeUnit.MILLISECONDS);}流程图业务线程 Watch Dog 定时任务 │ │ ▼ │ 获取锁成功 ──────────────────────► 启动定时任务 │ │ │ 执行业务代码 │ │ ▼ (每10秒) │ ┌──────────────────┐ │ │ 检查锁是否还被当前 │ │ │ 线程持有 │ │ └────────┬─────────┘ │ │ 是 │ ┌─────────▼─────────┐ │ │ 重置过期时间 30秒│ │ └──────────────────┘ │ │ ▼ │ 业务执行完毕 │ │ │ ▼ ▼ 释放锁 ──────────────────────► 取消定时任务什么时候 Watch Dog 不启动用户显式传递了leaseTime如tryLock(100, 10, TimeUnit.SECONDS)此时锁会在固定时间后自动释放不会续期六、锁释放流程释放锁同样通过 Lua 脚本完成-- KEYS[1]锁的名称-- KEYS[2]解锁消息频道-- ARGV[1]线程标识-- ARGV[2]锁的过期时间-- 1. 锁不存在 → 直接返回if(redis.call(exists,KEYS[1])0)thenreturnnil;end;-- 2. 不是当前线程持有 → 返回错误if(redis.call(hexists,KEYS[1],ARGV[1])0)thenreturnnil;end;-- 3. 重入次数 -1localcounterredis.call(hincrby,KEYS[1],ARGV[1],-1);-- 4. 次数 0 → 重置过期时间并返回if(counter0)thenredis.call(pexpire,KEYS[1],ARGV[2]);returnnil;end;-- 5. 次数 0 → 删除锁并发布解锁消息redis.call(del,KEYS[1]);redis.call(publish,KEYS[2],unlock message);returnnil;释放锁后通过Redis 的发布订阅Pub/Sub机制通知正在等待的线程重新尝试获取锁。七、可重入原理解析可重入是指同一个线程可以多次获取同一把锁而不会死锁。Redisson 的实现方式// 线程 A 第一次获取锁lock.lock();// Redis: myLock → { uuid:A:threadId: 1 }// 线程 A 再次获取同一把锁lock.lock();// Redis: myLock → { uuid:A:threadId: 2 } (重入次数 1)对应的 Lua 脚本逻辑if(redis.call(hexists,KEYS[1],ARGV[2])1)then-- 当前线程已持有锁 → 重入次数 1redis.call(hincrby,KEYS[1],ARGV[2],1);redis.call(pexpire,KEYS[1],ARGV[1]);returnnil;end;释放时同样需要释放两次lock.unlock();// 重入次数 2 → 1lock.unlock();// 重入次数 1 → 0 → 删除锁八、锁的阻塞重试机制当锁被其他线程持有时Redisson 不会立即返回失败而是订阅锁释放频道RedissonLockEntry订阅redisson_lock__channel:{lockName}循环等待使用Semaphore阻塞当前线程收到通知后重试锁释放后主线程发布消息唤醒等待线程// 订阅锁释放privateRFutureRedissonLockEntrysubscribe(longthreadId){returncommandExecutor.subscribe(getEntryName(),getChannelName());}// 等待信号量阻塞privatevoidawait(longttl,TimeUnitunit)throwsInterruptedException{entry.getLatch().tryAcquire(ttl,unit);}流程图解线程 A Redis 线程 B │ │ │ ├─ 获取锁成功 ──────────►│ │ │ │ │ │ │◄──── 尝试获取锁 ────────┤ │ │ │ │ ├─ 返回 ttl25000 ──────►│ │ │ │ │ │ 订阅锁释放频道 │ │ │ │ │ 进入阻塞等待 (Semaphore) │ │ │ ├─ 释放锁 ──────────────►│ │ │ │ │ │ ├─ PUBLISH 解锁消息 ────►│ │ │ │ │ │ 收到通知唤醒 │ │ │ │ │◄──── 再次尝试加锁 ────┤ │ │ │ │ ├─ 获取锁成功 ──────────►│九、其他分布式锁类型简介1. 公平锁FairLock保证线程按申请顺序获取锁内部使用 Redis 的 List 作为等待队列。2. 读写锁ReadWriteLock锁组合结果读 读✅ 并行不互斥读 写❌ 互斥写 写❌ 互斥适用场景读多写少的数据缓存、配置中心。3. 联锁MultiLock一次操作多个独立的 RLock要么全部成功要么全部失败类似“原子性”。适用于跨多个 Redis 实例的场景。4. 红锁RedLockRedis 官方提出的强一致性分布式锁算法要求半数以上 Redis 节点加锁成功才算成功。比普通联锁更严格。十、常见面试题汇总Q1Redisson 锁和 SETNX 手动实现的锁有什么区别特性RedissonSETNX 手动实现可重入✅ 支持❌ 需自己实现自动续期✅ Watch Dog❌ 需手动处理阻塞重试✅ 支持❌ 需自旋或轮询原子性✅ Lua 脚本⚠️ 需谨慎设计Q2Watch Dog 在多长时间续期一次默认锁过期时间 30 秒Watch Dog每 10 秒续期一次。Q3如果业务执行时间超过 30 秒但 Watch Dog 没启动会怎样锁会在 30 秒后自动释放其他线程会获取锁导致并发问题。解决方案传入足够大的leaseTime。Q4Redisson 锁是强一致性的吗不是。Redis 主从架构下主节点宕机可能导致锁丢失。如需强一致性使用RedLock算法但性能较低。Q5Lua 脚本为什么能保证原子性Redis 在执行 Lua 脚本期间不会执行任何其他命令脚本中的所有操作是一个不可分割的原子单位。总结Redisson 分布式锁的核心可以浓缩为Hash 存重入 Lua 保原子 订阅通知 Watch Dog 续期核心组件技术手段解决的问题数据结构Redis Hash存储线程标识和重入次数互斥性Lua 脚本保证加锁/解锁原子性可重入重入计数同一线程多次获取锁自动续期Watch Dog业务超时锁不释放阻塞重试Pub/Sub Semaphore避免无效自旋公平性等待队列按申请顺序获取锁掌握这些你不仅能搞定面试还能在生产环境自信地选择和使用 Redisson 分布式锁。The End点点关注收藏不迷路⬆ ⬆ 顶部 ⬆ ⬆

相关新闻