当前位置: 首页 > news >正文

Redisson 四大核心机制实现原理详解

一、可重入锁(Reentrant Lock)

可重入锁是什么?

  • 通俗定义

    可重入锁类似于一把“智能锁”,它能识别当前的锁持有者是否是当前线程:

    • 如果是,则允许线程重复获取锁(重入),并记录重入次数。
    • 如果不是,则其他线程必须等待锁释放后才能获取。
  • 典型场景

    当一个线程调用了一个被锁保护的方法A,而方法A内部又调用了另一个被同一锁保护的方法B时,如果锁不可重入,线程会在调用方法B时被自己阻塞(死锁)。可重入锁允许这种嵌套调用。

public class Demo {private final Lock lock = new SomeLock(); // 假设这是一个锁public void methodA() {lock.lock();try {methodB(); // 调用另一个需要加锁的方法} finally {lock.unlock();}}public void methodB() {lock.lock();try {// 业务逻辑} finally {lock.unlock();}}
}
  • 如果锁不可重入 线程进入methodA获取锁后,调用methodB时再次尝试加锁,会因为锁已被自己持有而永久阻塞(死锁)。
  • 如果锁可重入 线程在methodB中能成功获取锁,计数器从1增加到2,释放时计数器递减,最终正常释放。

实现原理:通过 Redis 的 Hash 结构实现线程级锁的可重入性。

  1. 数据结构

    • Key:锁名称(如 lock:order:1001)。
    • Field:客户端唯一标识(UUID + 线程ID),如 b983c153-7091-42d8-823a-cb332d52d2a6:1
    • Value:锁的 重入次数(初始为 1,重入时递增)。
  2. 加锁逻辑

    • 首次加锁:执行 Lua 脚本,若 Key 不存在,创建 Hash 并设置重入次数为 1。
      -- KEYS[1]=锁名, ARGV[1]=锁超时时间, ARGV[2]=线程唯一ID
      if (redis.call('exists', KEYS[1]) == 0) then  	 -- 如果锁不存在redis.call('hincrby', KEYS[1], ARGV[2], 1);  -- 创建Hash,记录线程重入次数redis.call('pexpire', KEYS[1], ARGV[1]);	 -- 设置锁超时时间return nil;									 -- 返回成功
      end;
      
    • 重入加锁:若 Field 匹配当前线程,重入次数 +1。
      if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then	-- 如果锁已被当前线程持有redis.call('hincrby', KEYS[1], ARGV[2], 1);			-- 增加重入次数redis.call('pexpire', KEYS[1], ARGV[1]);			-- 刷新锁超时时间return nil;											-- 返回成功
      end;
      
  3. 释放锁:减少重入次数,归零时删除 Hash。

    -- KEYS[1]: 锁名称(如 my_lock)
    -- KEYS[2]: 发布订阅的频道名
    -- ARGV[1]: 解锁消息标识(如 0)
    -- ARGV[2]: 锁的过期时间(毫秒)
    -- ARGV[3]: 客户端唯一标识(UUID + 线程ID)-- 检查锁是否存在且属于当前线程
    if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) thenreturn nil; -- 锁不存在或不属于当前线程,直接返回
    end;-- 减少重入计数器(原子操作)
    local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);if (counter > 0) then-- 仍有重入未释放完,更新锁过期时间redis.call('pexpire', KEYS[1], ARGV[2]);return 0; -- 返回0表示未完全释放
    else-- 计数器归零,删除锁并发布释放通知redis.call('del', KEYS[1]);redis.call('publish', KEYS[2], ARGV[1]);return 1; -- 返回1表示锁已完全释放
    end;
    

二、锁重试机制(Retry Mechanism)

重试机制的触发条件

当调用 tryLock(long waitTime, long leaseTime, TimeUnit unit) 方法时,若 waitTime > 0,Redisson 会启用重试机制。例如:

java// 10秒内不断重试获取锁,获取成功后持有锁60秒
lock.tryLock(10, 60, TimeUnit.SECONDS);

若首次获取锁失败,进入重试流程。

实现原理: 事件驱动优先,主动轮询兜底

  1. 首次尝试获取锁

    • 原子性操作:通过 Lua 脚本尝试获取锁(检查锁是否存在或是否属于当前线程)。
    • 失败返回值:若锁被其他线程持有,返回锁的剩余存活时间(ttl)。
  2. 订阅锁释放事件

    • 创建监听频道:订阅 Redis 频道 redisson_lock__channel:{lockName}
    • 事件驱动优化:避免频繁轮询,仅当锁释放时触发重试,减少无效请求
    // 伪代码:订阅锁释放事件
    RFuture<RedissonLockEntry> future = subscribe(lockName);
    RedissonLockEntry entry = get(future);
    
  3. 循环重试(主动轮询 + 事件触发)

    • 计算剩余等待时间:基于 waitTime 和已消耗时间,动态调整剩余等待窗口。
    • 双重检测逻辑
      • 主动轮询:定期(默认间隔 100ms ~ 300ms)执行 Lua 脚本尝试获取锁。
      • 事件触发:收到锁释放通知后立即尝试获取锁。
    • 退避策略:每次重试失败后,采用随机递增的等待时间(避免多个客户端同时竞争导致雪崩)。

    关键代码逻辑(简化)

long remainingTime = waitTime; // 剩余等待时间
long startTime = System.currentTimeMillis();while (remainingTime > 0) {// 1. 尝试获取锁Long ttl = tryAcquire(leaseTime, unit); // 调用Lua脚本if (ttl == null) {return true; // 获取成功}// 2. 计算剩余时间long elapsed = System.currentTimeMillis() - startTime;remainingTime -= elapsed;if (remainingTime <= 0) {break; // 超时退出}// 3. 等待锁释放事件或超时entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); // 基于信号量等待// 4. 更新剩余时间remainingTime -= (System.currentTimeMillis() - startTime - elapsed);
}
return false; // 超时未获取
  1. 超时终止
    • 时间窗口耗尽:若总耗时超过 waitTime,终止重试并返回失败。
    • 资源清理:取消 Redis 订阅,释放连接。

三、WatchDog 看门狗(锁续期机制)

防止业务执行时间超过锁的过期时间,导致锁提前释放。

启用看门狗需满足以下条件之一:

  • 未显式指定锁的租约时间(leaseTime): 例如调用 lock.tryLock()lock.lock() 时不传 leaseTime 参数。
  • 显式设置租约时间为 -1: 例如 lock.tryLock(10, -1, TimeUnit.SECONDS)

注意:若指定了固定的 leaseTime(如 lock.tryLock(10, 30, TimeUnit.SECONDS)),看门狗不会启动,锁会在 30 秒后自动释放。

实现原理:后台线程自动续期锁,防止业务未完成时锁过期。

  1. 触发条件:未指定锁超时时间(如 lock.lock())。

  2. 续期逻辑

    • 定时任务:默认每 10 秒(lockWatchdogTimeout / 3)续期一次。

    • 续期命令:重置锁的过期时间为 30 秒(默认值)。

      -- KEYS[1]: 锁名称
      -- ARGV[1]: 过期时间(默认30秒)
      -- ARGV[2]: 客户端唯一标识
      if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) thenredis.call('pexpire', KEYS[1], ARGV[1]);return 1;
      end;
      return 0;
      
  3. 终止条件

    • 锁被释放(unlock() 调用)。
    • 客户端断开连接或线程中断。

四、主从一致性(MultiLock/RedLock)

Redis 主从复制是异步的,若主节点宕机且锁未同步到从节点,可能导致多个客户端同时持有锁。

实现原理:基于多数派原则,向多个独立节点加锁。

  1. MultiLock 流程

    • 加锁:向所有节点发送加锁请求,需 半数以上成功(如 3 节点至少 2 个成功)。
    • 容错:允许最多 ⌊(N-1)/2⌋ 个节点故障(如 5 节点允许 2 个故障)。
    • 解锁:无论加锁是否成功,向所有节点发送解锁命令。
  2. RedLock 算法增强

    • 时钟同步:要求节点使用 NTP 同步时间,锁有效期需包含时钟漂移。
    • 加锁验证:计算加锁耗时,确保有效时间未耗尽。
  3. 配置示例

    RLock lock1 = redissonClient1.getLock("lock");
    RLock lock2 = redissonClient2.getLock("lock");
    RLock multiLock = new RedissonMultiLock(lock1, lock2);
    multiLock.lock();
    try {// 业务逻辑
    } finally {multiLock.unlock();
    }
    

五、总结
机制实现原理
可重入锁使用 Redis Hash 结构存储锁名、线程唯一标识(UUID+线程ID)和重入次数。同一线程多次获取锁时重入次数递增,释放时递减,归零后删除锁。
锁重试通过 Pub/Sub 订阅锁释放事件 避免轮询;失败后按退避策略(默认 1.5 秒)重试,直到超时或成功。
WatchDog后台线程每 10 秒(默认)检查锁持有状态,若锁存在则续期(重置过期时间至 30 秒)。未指定锁超时时间时自动启用。
主从一致性使用 MultiLock/RedLock:向多个独立节点加锁,需半数以上成功;解锁时向所有节点发送命令,解决主从异步复制导致的锁失效。

相关文章:

  • 多模块,依赖android.car.jar后,能调用接口但是没有回调的问题
  • 关于Redisson分布式锁的用法
  • 计算机网络 : Socket编程
  • Java(基础) day01 初识Java
  • window 显示驱动开发-分页视频内存资源
  • 从 Vue3 回望 Vue2:生命周期的清晰化——从混乱钩子到明确时机
  • 分布式锁: Redis和ZooKeeper两种分布式锁对比
  • 操作系统之进程和线程听课笔记
  • IOP出版|第二届人工智能、光电子学与光学技术国际研讨会(AIOT2025)
  • 深入解析ZAB协议:ZooKeeper的分布式一致性核心
  • 济南超算研究所面试问题
  • Elasticsearch 索引副本数
  • Git基础使用方法与命令总结
  • Python线性回归:从理论到实践的完整指南
  • 【时空图神经网络 交通】相关模型2:STSGCN | 时空同步图卷积网络 | 空间相关性,时间相关性,空间-时间异质性
  • vue复杂数据类型多层嵌套的监听
  • DDS(数据分发服务) 和 P2P(点对点网络) 的详细对比
  • Qwen2.5-VL模型sft微调和使用vllm部署
  • yocto项目例子
  • 美创科技针对《银行保险机构数据安全管理办法》解读
  • 美联储主席:供应冲击或更频繁,将重新评估货币政策方法中的通胀和就业因素
  • 第一集|好饭不怕晚,折腰若如初见
  • 黄仕忠丨戏曲文献研究之回顾与展望
  • 河南信阳拟发文严控预售许可条件:新出让土地开发的商品房一律现房销售
  • 广东韶关一镇干部冲进交通事故火海救人,获授“见义勇为”奖励万元
  • 浙江省机电集团党委书记、董事长廉俊接受审查调查