连载中 4/20

Redisson:可重入锁的完整实现

2026-06-26 · 2564 阅读 · 0 评论 · 0 赞

不会重入的锁,会自杀

上一篇攒出的三件套(SET NX PX 抢锁、Lua 释放、Lua 续期)看着完整,跑进真实业务立刻暴露一个缺口:锁不可重入。场景很日常——扣款入口方法上抢了锁 lock:card:8801,方法内部要调风控校验,风控方法上又标了同一把锁。线程第一层加锁成功,进到第二层再抢锁——锁就在自己手里,但 SET NX 一看 key 存在,拒绝——线程等自己释放锁,自己永远等不到,自己把自己锁死了。synchronized 和 ReentrantLock 都是可重入的,业务代码顺着 JVM 的习惯写,分布式锁不重入就是给自己埋雷。

Redisson 三行代码

Java 生态里没理由自己造这个轮子,Redisson 把上一篇的全部问题连同这篇的重入一起解决了:

RLock lock = redissonClient.getLock("lock:card:8801");
lock.lock();                 // 抢锁,不传 leaseTime,看门狗自动接管
try {
    // 临界区业务
} finally {
    lock.unlock();            // 必须放在 finally,异常也要释放
}

三行代码背后藏着一个关键纪律:unlock 必须放 finally——业务抛异常不释放锁,等于把永生锁的坑亲手挖回来。

Hash 结构:重入计数的载体

Redisson 为什么用 Hash 而不是 String 存锁?看它加锁的 Lua 逻辑(简化版):

-- KEYS[1]=锁名  ARGV[1]=客户端ID:线程ID  ARGV[2]=TTL毫秒
if redis.call("EXISTS", KEYS[1]) == 0 then
    redis.call("HSET", KEYS[1], ARGV[1], 1)        -- 空锁:首次加锁,计数=1
    redis.call("PEXPIRE", KEYS[1], ARGV[2])
    return nil
end
if redis.call("HEXISTS", KEYS[1], ARGV[1]) == 1 then
    redis.call("HINCRBY", KEYS[1], ARGV[1], 1)     -- 我的锁:重入,计数+1
    redis.call("PEXPIRE", KEYS[1], ARGV[2])
    return nil
end
return redis.call("PTTL", KEYS[1])                  -- 别人的锁:返回剩余过期时间

锁名做 key,持有人标识(客户端 ID 加线程 ID)做 field,重入次数做 value:lock:card:8801 → {"a1b2:thread-17": 2},意思是线程 17 进了两次临界区。解锁是反着来(也是 Lua):重入计数减一,减到零才真正 DEL——内层方法退锁只减计数,外层方法退锁才放钥匙,嵌套的进和出一一对应。顺便,第三个分支返回 PTTL(剩余过期时间)是个精巧设计:抢锁失败的线程知道「还有 4 秒过期」,据此安排重试节奏,不用傻傻轮询。

看门狗的开关藏在一个参数里

Redisson 默认 TTL 30 秒,看门狗每 10 秒(TTL 的三分之一)续一次,上一篇的机制原样内置。但有个容易翻车的细节:看门狗只在「不指定 leaseTime」时才启动。lock.lock() 走看门狗;lock.lock(10, TimeUnit.SECONDS) 指定了租期,Redisson 认为你「自己管理时长」,10 秒后锁准时过期,看门狗不续。很多事故就出在这一行——团队某人图省事传了个 leaseTime,业务一抖锁先没了,互斥破碎查半天。给个决策口诀:临界区时长不可控,用无参 lock 交给看门狗;时长确定且短(比如缓存重建),才用带 leaseTime 的定租。

用法TTL 行为适用场景
lock.lock()30 秒,看门狗每 10 秒续期业务时长不可控的默认选择
lock.lock(10, SECONDS)10 秒,无续期,到期自动释放时长确定,宁可锁过期也别卡死
lock.tryLock(2, 30, SECONDS)最多等 2 秒抢锁,租期 30 秒抢不到就放弃的限时任务

Redisson 家族里还有公平锁(按请求顺序排队)、读写锁(读共享写互斥)、联锁(多把锁同时加)等变体,思想同源不展开。值得记一笔的是它对 RedLock 的实现(RRedLock)——这是 Redis 官方作者为「锁在主从切换时丢失」开的药方,而这份药方恰恰是分布式锁历史上最著名的一场论战的主角。下一篇,把 RedLock 的来龙去脉和那场隔空交手讲透。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#分布式锁#Redisson#可重入锁#看门狗#Hash结构

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞