适用版本:Redisson
3.23.5+
核心组件:
RedissonLock
(可重入锁实现)涉及模块:Lua 脚本、Redis 发布订阅、内部续期机制(看门狗)
📌 一、Redisson 分布式锁实现简介
Redisson 基于 Redis 实现了多种分布式锁的高级封装,其中 RedissonLock
实现了类似 ReentrantLock
的可重入锁特性。它利用 Redis 的原子操作和发布订阅机制,配合 Lua 脚本、异步任务和线程 ID 维护锁状态。
📍 二、加锁流程源码解析
🔧 外部入口
java
RLock lock = redissonClient.getLock("myLock");
lock.tryLock(10, 30, TimeUnit.SECONDS); // 等待最多10秒,成功后30秒自动释放
会进入以下同步方法:
java
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException
🧩 Step 1:尝试加锁 tryAcquire(...)
java
Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
内部调用的是 Lua 脚本,核心逻辑:
lua
if lock not exists:
hset(lockName, threadId, 1)
pexpire(lockName, leaseTime)
else if same thread:
hincrby(lockName, threadId, 1)
pexpire(lockName, leaseTime)
else:
return TTL (锁被占用)
返回值:
null
:加锁成功ttl
:加锁失败,返回锁剩余时间
🧩 Step 2:加锁失败 → 订阅发布频道
java
CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
订阅的是该锁的发布频道:
java
String getChannelName() = prefix + ":{" + lockName + "}:channel"
底层通过 PubSubConnectionEntry
注册订阅,封装为 RedissonLockEntry
,包含 CountDownLatch
。
🧩 Step 3:循环等待并重试加锁
java
while (true) {
Long ttl = tryAcquire(...)
if (ttl == null) return true;
latch.tryAcquire(ttl or remainTime)
}
每次被唤醒后再次尝试加锁,直到:
- 成功获取锁
- 超时返回 false
- 异常终止
📍 三、解锁流程源码解析
🔧 unlock() → unlockInnerAsync(...)
java
public void unlock() {
get(unlockInnerAsync(threadId));
}
核心解锁逻辑为:
java
protected RFuture<Boolean> unlockInnerAsync(...) {
return evalWrite(..., Lua脚本, keys, args);
}
🧩 Lua 脚本执行释放逻辑
lua
if hexists(lock, threadId) == 0:
return nil
counter = hincrby(lock, threadId, -1)
if counter > 0:
pexpire(lock, leaseTime)
else:
del(lock)
publish(channel, unlockMessage) -- 唤醒其他订阅者
✅ 只有当线程重入次数减到 0 时,才真正释放锁并发布消息
🧩 发布消息通知其他等待线程
java
redis.call(ARGV[4], KEYS[2], ARGV[1])
即:
redis
PUBLISH <channel> <unlockMessage>
由 LockPubSub
监听该频道,唤醒之前 subscribe()
创建的 RedissonLockEntry.getLatch()
。
📍 四、看门狗机制(Watchdog)
若调用如下代码:
java
lock.lock(); // 不指定 leaseTime
Redisson 会自动启用看门狗机制,每隔 10s
续约一次(默认租期 30s
):
java
scheduleExpirationRenewal(threadId)
核心续约命令:
java
pexpire(lockKey, 30000)
续期任务在:
- 锁未释放前持续运行
- 线程释放锁后自动取消
✅ 五、源码流程图(文字版)
scss
┌────────────────────────┐
│ tryLock() │
└────────────┬───────────┘
↓
┌────────────────────────┐
│ tryAcquire (Lua脚本) │
│ - 加锁成功 → 返回null │
│ - 失败 → 返回TTL时间 │
└────────────┬───────────┘
↓
┌────────────────────────────┐
│ subscribe + latch等待 │
│ 订阅释放频道,等待通知 │
└────────────┬───────────────┘
↓
┌────────────────────────┐
│ 收到消息重新tryAcquire │
│ 成功 → true 失败→重试 │
└────────────┬───────────┘
↓
┌────────────────────────────┐
│ unlock() │
│ - hincrby重入次数减1 │
│ - 发布unlock通知 │
└────────────────────────────┘
📍 六、小结与建议
特性 | 描述 |
---|---|
加锁脚本 | Lua 保证原子性,兼容可重入逻辑 |
解锁通知 | Redis 发布订阅频道通知其他线程唤醒尝试加锁 |
看门狗续期 | 解决业务执行过长导致锁过期问题(自动延期) |
支持公平锁 | Redisson 另有 RedissonFairLock 实现公平队列(推荐高并发下使用) |
📚 七、推荐阅读源码类
RedissonLock
:核心加锁解锁逻辑LockPubSub
:发布订阅机制CommandAsyncExecutor
:命令异步封装RenewalTask
:看门狗定时器逻辑tryAcquireAsync0()
:真正的加锁内部实现evalWrite(...)
:执行 Lua 脚本的统一方法