一、Redisson 可重入锁实现原理
1. Redis 数据结构
- 使用 Hash 存储锁信息:
KEY: "lock_name" FIELD: "client_uuid:thread_id"(客户端ID + 线程ID) VALUE: 重入次数(整数)
2. 加锁逻辑(Lua 脚本原子操作)
-- 参数:KEYS[1]=锁名, ARGV[1]=过期时间, ARGV[2]=客户端标识
if redis.call('exists', KEYS[1]) == 0 then
redis.call('hset', KEYS[1], ARGV[2], 1) -- 首次加锁
redis.call('pexpire', KEYS[1], ARGV[1]) -- 设置过期时间
return 1
end
if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then
redis.call('hincrby', KEYS[1], ARGV[2], 1) -- 重入次数+1
redis.call('pexpire', KEYS[1], ARGV[1]) -- 刷新过期时间
return 1
end
return 0 -- 加锁失败
3. 看门狗机制(Watchdog)
- 后台守护线程每10秒检查锁(默认)
- 若当前线程仍持有锁,自动续期(默认续到30秒)
new Timer().schedule(new TimerTask() {
public void run() {
if (isLockHeld()) {
redis.expire(key, 30, SECONDS); // 续期
}
}
}, 10000, 10000); // 每10秒执行
4. 解锁逻辑
if redis.call('hexists', KEYS[1], ARGV[2]) == 0 then
return nil -- 非锁持有者
end
local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1) -- 重入次数-1
if counter > 0 then
redis.call('pexpire', KEYS[1], ARGV[1]) -- 只减少计数
else
redis.call('del', KEYS[1]) -- 彻底删除锁
redis.publish('unlock_channel', 'release') -- 通知等待线程
end
return 1
二、Redisson 公平锁实现原理
1. 双向队列结构
- 锁队列:
lock_name:queue(List结构存储等待线程) - 线程节点:
lock_name:queue:client_uuid:thread_id(Hash结构存储状态)
2. 加锁流程
-
加入队列
RPUSH lock_name:queue "client_uuid:thread_id" -
检查是否为队头
if redis.call('lindex', 'lock_name:queue', 0) == MY_ID then return acquire_lock() end -
订阅排队通知
redisson.getTopic("lock_name:channel").subscribe(MY_ID);
3. 锁传递机制
graph LR
A[持有锁的线程] -->|释放锁| B[发布通知]
B --> C[队列中的下个节点]
C -->|收到通知| D[尝试获取锁]
D -->|成功| E[更新队列头]
4. 解锁逻辑
redis.call('LREM', 'lock_name:queue', 1, MY_ID)
redis.publish('lock_name:channel', NEXT_CLIENT_ID)
三、关键对比
| 特性 | 可重入锁 | 公平锁 |
|---|---|---|
| 数据结构 | Hash | List + Hash + Pub/Sub |
| 排队机制 | 无(随机抢占) | FIFO 队列 |
| 性能 | 高(直接操作) | 较低(需维护队列) |
| 锁传递方式 | 超时自动释放 | 显式通知下一个线程 |
| 适用场景 | 高并发非严格顺序 | 顺序执行要求较高的敏感场景 |
| Redis 命令数 | 2~3条 | 5~8条(含队列维护和通知) |
四、底层机制共性
- 所有核心操作使用 Lua 脚本,保障原子性。
- 使用客户端标识:UUID + threadId 唯一标识持锁线程。
- 默认30秒自动超时释放,防止死锁。
- 通过时钟漂移检测与守护线程机制应对网络分区或GC暂停等问题。
⚠️ 注意事项:
- 公平锁实现复杂度是可重入锁的3倍以上。
- Redis Cluster 下 Pub/Sub 存在跨节点不可靠问题。
- 建议开启
redisson-lock__timeout检查保障稳定性。