Redisson 可重入锁(Reentrant Lock)与公平锁(Fair Lock)实现原理

229 阅读2分钟

一、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. 加锁流程

  1. 加入队列

    RPUSH lock_name:queue "client_uuid:thread_id"
    
  2. 检查是否为队头

    if redis.call('lindex', 'lock_name:queue', 0) == MY_ID then
        return acquire_lock()
    end
    
  3. 订阅排队通知

    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)

三、关键对比

特性可重入锁公平锁
数据结构HashList + Hash + Pub/Sub
排队机制无(随机抢占)FIFO 队列
性能高(直接操作)较低(需维护队列)
锁传递方式超时自动释放显式通知下一个线程
适用场景高并发非严格顺序顺序执行要求较高的敏感场景
Redis 命令数2~3条5~8条(含队列维护和通知)

四、底层机制共性

  1. 所有核心操作使用 Lua 脚本,保障原子性。
  2. 使用客户端标识:UUID + threadId 唯一标识持锁线程。
  3. 默认30秒自动超时释放,防止死锁。
  4. 通过时钟漂移检测与守护线程机制应对网络分区或GC暂停等问题。

⚠️ 注意事项:

  • 公平锁实现复杂度是可重入锁的3倍以上。
  • Redis Cluster 下 Pub/Sub 存在跨节点不可靠问题。
  • 建议开启 redisson-lock__timeout 检查保障稳定性。