Redisson分布式锁的实现原理与加锁机制(含可视化)
Redisson 基于 Redis 提供了健壮的分布式锁实现,核心由原子化 Lua 脚本、可重入计数、看门狗自动续期、自旋重试、以及在集群环境下的路由与脚本缓存优化共同构成。本文在校正与扩展原有资料的基础上,系统性梳理其原理与工程实践,并附带配色优化的 Mermaid 图以提升可读性。Redisson 分布式锁以工程化手段解决了“原子性、持有者识别、长耗时续期、重入与释放安全”等关键问题。理解其在
·
Redisson分布式锁的实现原理与加锁机制(含可视化)
概述
Redisson 基于 Redis 提供了健壮的分布式锁实现,核心由原子化 Lua 脚本、可重入计数、看门狗自动续期、自旋重试、以及在集群环境下的路由与脚本缓存优化共同构成。本文在校正与扩展原有资料的基础上,系统性梳理其原理与工程实践,并附带配色优化的 Mermaid 图以提升可读性。
简介与项目背景
- 业务痛点:多实例并发下的资源互斥、临界区保护、幂等控制。
- 传统问题:仅依赖 setnx/expire 容易出现“非原子”与“误释放”问题。
- Redisson 解法:以 Lua 保证原子性,以哈希结构记录重入,以看门狗解决长耗时,以自旋保证获得锁,以 EVALSHA 提升脚本执行性能。
名词解释
- 客户端ID:通常为 UUID + “:” + threadId,用于标识“谁持有锁”。例如
UUID.randomUUID()与线程ID拼接。 - 可重入锁:同一线程在持有锁的情况下可以再次进入临界区,计数 +1;释放时计数 -1 直至 0 才真正释放。
- 看门狗(watchdog):锁持有期间的后台续期任务,默认每隔 10 秒检查并延长过期。
- EVAL/EVALSHA:Redis 执行 Lua 脚本的两种方式;EVALSHA 通过脚本摘要命中缓存,减少传输与编译开销。参见
EVALSHA。 - TTL/PTTL:键剩余存活时间(毫秒级为 PTTL);用于判定何时可以重试加锁。
架构原理图
加锁机制(核心流程校正与拆解)
- 选路到 Master:在 Redis Cluster 中,客户端根据 key 的哈希槽选择目标 Master 节点,避免跨节点操作。
- 原子加锁 Lua 逻辑:使用 Lua 保证“检查 + 设置 + 过期 + 重入计数”的整体原子性:
- 若 key 不存在:创建哈希结构,字段为客户端ID,值为重入计数 1;同时设置过期(默认 30s)。
- 若 key 存在且字段为本客户端ID:执行可重入,计数 +1,并刷新过期。
- 若 key 存在但持有者非本客户端:返回剩余 TTL(PTTL),用于指导下一次重试间隔。
- 自旋与退避:客户端循环尝试加锁,可根据 PTTL 实现“精准睡眠”,避免无意义忙等。
- 看门狗续期:锁成功持有后,后台任务每 10 秒续期一次,确保业务超过初始过期时间仍不丢锁。
- 释放锁:减计数;当减至 0 时删除 key;非持有者不可释放,避免“误删他人锁”。
Lua 逻辑语义解读(无代码版)
- exists(KEYS[1]) == 0 → hset(KEYS[1], ARGV[2], 1); pexpire(KEYS[1], ARGV[1]); return nil
- hexists(KEYS[1], ARGV[2]) == 1 → hincrby(KEYS[1], ARGV[2], 1); pexpire(KEYS[1], ARGV[1]); return nil
- 其他情况 → return pttl(KEYS[1]) 以指导等待时长
EVALSHA 优化机制与伪代码示意
- 首次执行:发送完整 Lua,Redis 缓存并返回 SHA1。
- 后续执行:优先以 SHA1 执行;若未命中缓存,再回退完整 Lua 并重建缓存。
- 客户端侧:预计算并本地缓存内置 Lua 的 SHA1 摘要,减少运行时计算。
- 集群侧:每个 Master 独立缓存脚本;客户端需针对不同节点维护脚本摘要命中。
- 伪代码引用:
redis.evalsha()/redis.eval()/ 异常NoScriptException
续期机制(Watchdog)工作原理
- 触发条件:当线程成功获得锁且仍在执行临界区。
- 执行频率:默认每 10 秒(可配置),调用 pexpire 刷新 TTL 至设定值(如 30 秒)。
- 结束条件:线程完成业务并释放锁,或线程中断/应用停止(需配合健壮的释放逻辑)。
- 设计要点:续期必须与“持有者身份”绑定,防止非持有者续期导致锁僵死。
可重入锁实现要点
- 结构选择:使用 Redis Hash,field = 客户端ID,value = 重入计数。
- 加锁路径:本客户端持有时计数 +1;初次持有时设置过期。
- 释放路径:本客户端计数 -1;当计数为 0 → del 整个 key。
- 并发安全:所有操作在 Lua 中原子执行,避免并发条件竞争。
释放锁的安全性
- 核验持有者:仅当 hexists(KEYS[1], ARGV[2]) == 1 才允许 hincrby -1 或 del。
- 防误删:若不是持有者,直接返回,避免删除他人锁导致“临界区暴露”。
- 最终删除:计数归零时 del key;否则继续保持锁与 TTL。
时序图(获取锁→续期→释放)
容错与边界场景
- 时钟漂移与长 GC:续期线程被阻塞可能导致 TTL 过期,需合理设置过期与续期间隔,并监控 GC 暂停。
- 网络分区:客户端误以为释放失败或续期失败,建议增加重试与幂等保护。
- 主从复制延迟:锁写入在主库,读写一致性以主为准,避免从库读取造成误判。
- 进程崩溃:看门狗停止,TTL 到期自动释放,保证锁不会永久占用。
- 阻塞临界区:单点长事务应尽量分解,缩短持锁时间,避免对系统整体吞吐的影响。
性能与实践建议
- 设置合理 TTL:初始 30s 仅为默认;结合业务耗时与续期频率动态评估。
- 精准退避:根据返回的 PTTL 进行 sleep,降低无效重试。
- 指标与日志:记录“加锁成功率、等待时长、持锁时长、续期次数、释放失败数”等关键指标。
- 统一封装:对外暴露统一的加锁接口,内部包含重试、续期、释放、异常处理与监控。
高级话题与对比
- 公平锁 vs 非公平锁:Redisson 默认非公平;公平锁需要排队队列语义,吞吐与延迟权衡。
- 读写锁:读锁共享、写锁互斥;适用于读多写少场景,但实现复杂度更高。
- RedLock(多主多副本):理论争议较大,工程实践需谨慎评估网络分区与延迟,参考官方讨论。
与 Java API 的对应关系(名称引用)
- 获取与释放:
RLock.lock()/RLock.unlock() - 尝试加锁:
RLock.tryLock()(支持等待与租期) - 自动续期:
Watchdog线程负责续期,结合pexpire
参考资料与权威文献
- Redis 官方 Lua/EVAL/EVALSHA 文档:https://redis.io/docs/latest/develop/reference/scripting/
- Redis Keyspace TTL/PTTL 文档:https://redis.io/commands/pttl/
- Redisson 官方文档与源码(GitHub):https://github.com/redisson/redisson
- Redis Cluster 介绍与槽位:https://redis.io/docs/latest/operate/cluster/
- RedLock 原论文与讨论(antirez):https://redis.io/docs/latest/develop/use/patterns/distributed-locks/
速记口(便于复述与面试)
- 一句概括:Lua 原子 + Hash 重入 + Watchdog 续期 + EVALSHA 缓存 + 自旋退避 + 主库路由。
- 关键流程:exists → hset/hexists → hincrby → pexpire → 返回 pttl → 自旋 → 释放减计数/删除。
- 风险点:长 GC/网络分区/主从延迟/错误释放;监控与退避不可少。
- 配置要点:合理 TTL 与续期频率;精准自旋;健壮释放;统一封装与度量。
结语
Redisson 分布式锁以工程化手段解决了“原子性、持有者识别、长耗时续期、重入与释放安全”等关键问题。理解其在集群中的脚本缓存与路由细节、在边界场景下的容错策略,才能做到知其然并知其所以然,在真实生产环境中稳定发挥作用。
更多推荐

所有评论(0)