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:向多个独立节点加锁,需半数以上成功;解锁时向所有节点发送命令,解决主从异步复制导致的锁失效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/83858.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

srs-7.0 支持obs推webrtc流

demo演示 官方教程: https://ossrs.net/lts/zh-cn/blog/Experience-Ultra-Low-Latency-Live-Streaming-with-OBS-WHIP 实现原理就是通过WHIP协议来传输 SDP信息 1、运行 ./objs/srs -c conf/rtc.conf 2、obs推流 3、web端播放webrtc流 打开web:ht

面试题——JDBC|Maven|Spring的IOC思想|DI思想|SpringMVC

目录 一、JDBC 1、jdbc连接数据库的基本步骤&#xff08;掌握**&#xff09; 2、Statement和PreparedStatement的区别 &#xff08;掌握***&#xff09; 二、Maven 1、maven的作用 2、maven 如何排除依赖 3、maven scope作用域有哪些&#xff1f; 三、Spring的IOC思想 …

从代码学习数学优化算法 - 拉格朗日松弛 Python版

文章目录 前言1. 问题定义 (Problem Definition)2. 拉格朗日松弛 (Lagrangian Relaxation)3. 拉格朗日对偶问题 (Lagrangian Dual)4. 次梯度优化 (Subgradient Optimization)5. Python 代码实现导入库和问题定义辅助函数:求解拉格朗日松弛子问题次梯度优化主循环结果展示与绘图…

密码学实验

密码学实验二 一、实验目的&#xff08;本次实验所涉及并要求掌握的知识点&#xff09; 掌握RSA算法的基本原理并根据给出的RSA算法简单的实现代码源程序,以及能够使用RSA对文件进行加密。掌握素性测试的基本原理&#xff0c;并且会使用Python进行简单的素性测试以及初步理解…

力扣面试150题-- 从中序与后序遍历序列构造二叉树

Day 44 题目描述 思路 这题类似与昨天那题&#xff0c;首先来复习一下&#xff0c;后序遍历&#xff0c;对于后序遍历每一个元素都满足以下规律&#xff1a; &#xff08;左子树&#xff09;&#xff08;右子树&#xff09;&#xff08;根&#xff09;&#xff0c;那么我们直…

2区组的2水平析因实验的混区设计

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著傅珏生译)第7章2k析因的区组化和混区设计第7.4节的python解决方案。本文尽量避免重复书中的理论&#xff0c;着于提供python解决方案&#xff0c;并与原书的运算结果进行对比。您可以从Detail 下载实验设计与分析…

反向传播算法——矩阵形式递推公式——ReLU传递函数

总结反向传播算法。 来源于https://udlbook.github.io/udlbook/&#xff0c;我不明白初始不从 x 0 \boldsymbol{x}_0 x0​开始&#xff0c;而是从 z 0 \boldsymbol{z}_0 z0​开始&#xff0c;不知道怎么想的。 考虑一个深度神经网络 g [ x i , ϕ ] g[\boldsymbol{x}_i, \bold…

2025年PMP 学习二十三 16章 高级项目管理

2025年PMP 学习二十三 16章 高级项目管理 文章目录 2025年PMP 学习二十三 16章 高级项目管理高级项目管理战略管理战略管理的组成要素&#xff1a;企业战略转化为战略行动的阶段&#xff1a; 组织战略类型战略组织类型组织级项目管理OPM&#xff08;公司项目管理&#xff09; 组…

Journal of Real-Time Image Processing 投稿过程

投稿要求双栏12页以内(包括参考文献)&#xff0c;这个排版要求感觉不是很严格&#xff0c;我当时就是用普通的双栏的格式去拍的版&#xff0c;然后就提交了&#xff0c;也没单独去下载模版。 投稿过程 12.12 Submission received 12.12 Submission is under technical check 1…

t检验详解:原理、类型与应用指南

t检验详解&#xff1a;原理、类型与应用指南 t检验&#xff08;t-test&#xff09;是一种用于比较两组数据均值是否存在显著差异的统计方法&#xff0c;适用于数据近似正态分布且满足方差齐性的场景。以下从核心原理、检验类型、实施步骤到实际应用进行系统解析。 一、t检验的…

Web4X·AI实业未来家庭普及产品矩阵

Web4XAI实业未来家庭普及产品矩阵 > 打造一个“AI能干活、人更自由”的超级生活系统&#xff08;web4-web4.0&#xff09; 一、AI生活服务类 1、代表产品&#xff1a;  AI语音助手&#xff08;对话、提醒、天气、家庭调度&#xff09;  AI陪护机器人&#xff08;老…

Centos上搭建 OpenResty

一、OpenResty简介 OpenResty 是基于 Nginx 的扩展平台&#xff0c;完全兼容 Nginx 的核心功能&#xff08;如 HTTP 服务和反向代理&#xff09;&#xff0c;同时通过内嵌 LuaJIT 支持&#xff0c;允许开发者用 Lua 脚本灵活扩展业务逻辑。它简化了动态逻辑的实现。 二、安装…

项目管理进阶:基于IPD流程的项目管理部分问题及建议书【附全文阅读】

该文档主要探讨了研发项目管理中存在的问题及改进建议。指出项目组织、项目计划、项目监控等方面存在的问题&#xff0c;并给出了相应的设计要点。建议建立跨部门、全流程的项目计划体系&#xff0c;加强风险管理&#xff0c;引入科学的估计方法&#xff0c;建立项目历史数据积…

JVM之GC常见的垃圾回收器

收集器适用区域特点适用场景Serial新生代单线程&#xff0c;STW&#xff08;Stop-The-World&#xff09;客户端小应用Parallel Scavenge新生代多线程&#xff0c;吞吐量优先后台计算任务ParNew新生代Serial 的多线程版配合 CMS 使用CMS老年代并发标记&#xff0c;低延迟响应优先…

免费私有化部署! PawSQL社区版,超越EverSQL的企业级SQL优化工具面向个人开发者开放使用了

1. 概览 1.1 快速了解 PawSQL PawSQL是专注于数据库性能优化的企业级工具&#xff0c;解决方案覆盖SQL开发、测试、运维的整个流程&#xff0c;提供智能SQL审核、查询重写优化及自动化巡检功能&#xff0c;支持MySQL、PostgreSQL、Oracle、SQL Server等主流数据库及达梦、金仓…

HTTP/HTTPS与SOCKS5协议在隧道代理中的兼容性设计解析

目录 引言 一、协议特性深度对比 1.1 协议工作模型差异 1.2 隧道代理适配难点 二、兼容性架构设计 2.1 双协议接入层设计 2.2 统一隧道内核 三、关键技术实现 3.1 协议转换引擎 3.1.1 HTTP→SOCKS5转换 3.1.2 SOCKS5→HTTP转换 3.2 连接管理策略 3.2.1 智能连接池 …

3DGS——基础知识学习笔记

1.什么是3D高斯泼溅&#xff08;3D Gaussian Splatting&#xff09;&#xff1f; 目标&#xff1a;从一组稀疏的3D点&#xff08;比如通过相机或激光雷达采集的点云&#xff09;重建出高质量的3D场景&#xff0c;并支持实时渲染。 核心思想&#xff1a;用许多“3D高斯分布”&…

【C++】不推荐使用的std::allocator<void>

文章目录 不推荐使用的std::allocator<void>1. 核心区别2. 成员函数对比(1) allocate 和 deallocate(2) construct 和 destroy 3. 设计动机(1) std::allocator<T>(2) std::allocator<void> 4. 使用场景示例(1) std::allocator<int>(2) std::allocator&…

Go 语言云原生微服务全栈实战:Docker 镜像优化、K8s 编排与 Istio 流量治理

本系列文章将以 Go 语言为主导开发语言&#xff0c;系统性地讲解如何从零构建一个基于微服务架构的应用系统&#xff0c;涵盖以下核心模块&#xff1a; 使用 Go 构建高性能微服务构建精简且高效的 Docker 镜像利用 Kubernetes 进行微服务编排与部署通过 Istio 实现微服务的流量…

windows下authas调试tomcat

一般情况下&#xff0c;我们只需要输入以下代码 java -jar authas.jar调试tomcat时需要加上进程号 java -jar authas.jar <PID> 此外&#xff0c;如果你使用的是 Java 11 或更高版本&#xff0c;你需要添加 --add-opens 参数&#xff0c;以便 Arthas 能够访问 JVM 的内…