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

详细分析Redisson分布式锁中的renewExpiration()方法

目录

一、Redisson分布式锁的续期

整体分析

具体步骤和逻辑分析

为什么需要递归调用?

定时任务的生命周期?


一、Redisson分布式锁的续期

Redisson是一个基于Redis的Java分布式锁实现。它允许多个进程或线程之间安全地共享资源。为了实现这一点,Redisson使用了一种基于分布式系统的锁机制,其中锁的持有者在操作过程中需要维护锁的有效性。

关于Redisson分布式锁的详细介绍,可移步到我的另一篇博客Redisson分布式锁-CSDN博客

在Redisson中,锁的续期是一个关键特性,用于确保在锁的持有者仍在执行任务期间,锁不会被意外释放。

整体分析

锁的续期机制在Redisson中是自动管理的,锁的续期是基于一个定时任务的机制,定期检查锁的状态并决定是否需要续期。具体实现为:

private void renewExpiration() {// 1、首先会从EXPIRATION_RENEWAL_MAP中获取一个值,如果为null说明锁可能已经被释放或过期,因此不需要进行续期,直接返回ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee == null) {return;}// 2、基于TimerTask实现一个定时任务,设置internalLockLeaseTime / 3的时长进行一次锁续期,也就是每10s进行一次续期。Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {// 从EXPIRATION_RENEWAL_MAP里获取一个值,检查锁是否被释放ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());// 如果为null则说明锁也被释放了,不需要续期if (ent == null) {return;}// 如果不为null,则获取第一个thread(也就是持有锁的线程)Long threadId = ent.getFirstThreadId();if (threadId == null) {return;}// 如果threadId 不为null,说明需要续期,它会异步调用renewExpirationAsync(threadId)方法来实现续期RFuture<Boolean> future = renewExpirationAsync(threadId);// 处理结果future.onComplete((res, e) -> {// 如果有异常if (e != null) {log.error("Can't update lock " + getName() + " expiration", e);return;}// 如果续期成功,则会重新调用renewExpiration()方法进行下一次续期if (res) {// reschedule itselfrenewExpiration();}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);
}

具体步骤和逻辑分析

ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee == null) {return;}

首先,从 EXPIRATION_RENEWAL_MAP 中获取当前锁的 ExpirationEntry 对象。如果该对象为null,说明锁可能已经被释放或过期,因此不需要进行续期,直接返回。

Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {...}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

如果当前锁的 ExpirationEntry 对象不是null,就会继续往下执行,创建一个定时任务。这个定时任务的代码实现了一个锁的续期机制,具体步骤和逻辑分析如下:

在代码中,定时任务是通过 commandExecutor.getConnectionManager().newTimeout(...) 方法创建的,该任务的延迟时间设置为 internalLockLeaseTime / 3 毫秒,即每次续期的时间间隔。

ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ent == null) {return;
}

在定时任务的 run 方法中,首先尝试从 EXPIRATION_RENEWAL_MAP 中获取与当前锁对应的 ExpirationEntry 实例。如果获取到的 ExpirationEntry 为 null,则说明锁已经被释放,此时无需续期,直接返回。

Long threadId = ent.getFirstThreadId();
if (threadId == null) {return;
}

如果获取到的 ExpirationEntry 不为 null,说明如果锁仍然有效,继续往下走,接下来获取持有该锁的线程 ID。如果 threadId 为 null,也说明锁可能已经被释放,直接返回。

RFuture<Boolean> future = renewExpirationAsync(threadId);

如果持有锁的线程 ID 不为 null,继续往下走,则调用 renewExpirationAsync(threadId) 方法异步续期锁的有效期。

继续进入这个renewExpirationAsync()方法,可以看到,方法的主要功能是延长锁的有效期。下面是对这段代码的详细分析:

protected RFuture<Boolean> renewExpirationAsync(long threadId) {return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +"redis.call('pexpire', KEYS[1], ARGV[1]); " +"return 1; " +"end; " +"return 0;",Collections.singletonList(getName()),internalLockLeaseTime, getLockName(threadId));}

renewExpiration()函数内部的RFuture<Boolean> future = renewExpirationAsync(threadId);又是一个关键的函数,跳入renewExpirationAsync(threadId)内部一探究竟。

  • 返回类型RFuture<Boolean> 表示该方法返回一个表示异步操作结果的未来对象,最终会得到一个布尔值,指示续期操作是否成功。
  • 参数long threadId 是持有锁的线程 ID,用于标识当前续期操作是否适用于该线程。

这个renewExpirationAsync()是一个异步刷新有效期的函数,它主要是用evaLWriteAsync()方法来异步执行一段Lua脚本,重置当前threadId线程持有的锁的有效期。也就是说该方法负责执行给定的Lua脚本,以实现分布式锁的续期

  • KEYS[1]:代表锁的名称,即 Redis 键。
  • ARGV[1]:引用传入的第一个非键参数,表示希望设置的新过期时间(毫秒),锁的默认租约时间为internalLockLeaseTime。
  • ARGV[2]:引用传入的第二个非键参数,表示通过getLockName(threadId)根据线程ID生成特定的锁标识符,确保操作的是特定线程的锁。简单说就是持有锁的线程id
  • getName():获取当前锁的名称,用于作为Redis中的键。
  • LongCodec.INSTANCE:编码器,指示如何处理数据的序列化与反序列化。
  • RedisCommands.EVAL_BOOLEAN:表示执行的命令类型,这里是执行一个返回布尔值的Lua脚本。

Lua脚本中,首先执行redis.call('hexists', KEYS[1], ARGV[2]) == 1,该命令检查锁的名称KEYS[1]下是否存在持有该锁的线程ID(ARGV[1])。如果存在,说明该线程仍然是锁的持有者,则调用pexpire命令redis.call('pexpire', KEYS[1], ARGV[1])更新锁的过期时间。如果续期成功,返回1,否则返回0。

因此,Lua脚本中的整体逻辑是如果当前key存在,说明当前锁还被该线程持有,那么就重置过期时间为30s,并返回true表示续期成功,反之返回false。

这段代码的设计充分利用了Redis的Lua脚本特性,实现了高效且原子化的锁续期逻辑,减少了并发操作中的 race condition 问题,同时提供了异步执行的能力,提升了系统的响应性和性能。

然后,我们退回到renewExpiration()方法中,继续往下走,

future.onComplete((res, e) -> {if (e != null) {log.error("Can't update lock " + getName() + " expiration", e);return;}if (res) {renewExpiration();}
});

通过 onComplete 方法处理续期操作的结果,如果e 不为 null,说明有异常则记录错误日志。如果res 为 true,说明续期成功则调用 renewExpiration() 方法,安排下一次的续期操作。

总结一下,整体流程就是,在代码中,定时任务是通过 commandExecutor.getConnectionManager().newTimeout(...) 方法创建的。该任务会在指定的时间(internalLockLeaseTime / 3 毫秒)后执行一次。每当任务执行时,都会检查当前锁的状态,并尝试续期。如果需要续期(即锁仍然有效),则会调用 renewExpiration() 方法。

为什么需要递归调用?

在锁的实现中,为了确保锁在持有者处理任务期间保持有效,通常会设置一个有效期(lease time)。在有效期内,如果持有锁的线程仍然在执行任务,那么它需要定期续期,以防止在任务完成前锁过期,从而导致其他线程获取锁。

递归调用的机制:在 run 方法的最后,如果续期成功,调用 renewExpiration() 方法。这通常意味着该方法会重新安排另一个定时任务,相当于在每次续期后再次创建一个新的定时任务,使得续期操作可以持续进行。这种递归调用的方式确保了只要锁仍然被持有,续期操作就会不断地被调度,从而保持锁的有效性。

定时任务的生命周期?

每个定时任务的生命周期是短暂的,完成一次 run 方法的执行后,该任务就结束了。然后,通过递归调用,可能会创建新的定时任务,从而继续续期。

(1)任务通过 newTimeout 被创建,并且首次执行会在 internalLockLeaseTime / 3 毫秒后触发。这个时间间隔确保了任务在锁的生命周期的早期进行检查和续期。此时,任务进入其生命周期,准备执行。

(2)当定时任务第一次执行时,run() 方法被调用。它主要的任务是:

  1. 从 EXPIRATION_RENEWAL_MAP 获取锁的状态。
  2. 如果锁被释放(ent == null),任务直接返回,不再进行续期。
  3. 如果锁仍然存在并且当前线程持有锁(threadId != null),则异步调用 renewExpirationAsync(threadId) 来续期锁。
  4. 在续期的异步任务完成后,如果续期成功(res == true),会重新调用 renewExpiration() 进行下一次续期。

(3)续期条件:如果任务成功续期,它会在异步任务的 onComplete 回调中再次调用 renewExpiration() 方法。renewExpiration() 负责创建一个新的定时任务,这意味着每次任务续期成功后,系统会重新调度一个新的定时任务,以确保锁的有效期能够持续。

这个 renewExpiration() 方法的调用实际上是递归调用新的定时任务,续期继续进行下去。每次任务执行后,都可能会创建一个新的任务,直到锁被释放。

(3)定时任务的生命周期可能在以下情况下终止:

  • 锁被释放:当 EXPIRATION_RENEWAL_MAP.get(getEntryName()) 返回 null,表示锁已经被释放,定时任务会停止续期,不再创建新的定时任务。
  • 无持有锁的线程:如果没有线程持有锁(即 threadId == null),任务也会停止续期。
  • 异步任务失败:如果续期的异步任务失败(例如网络问题、数据库问题等),则可能无法继续续期。不过在代码中,如果发生异常,它只会记录错误,并不会立即停止整个续期机制,但最终续期将会失败并终止。

定时任务的生命周期从它的创建开始,通过定期执行检查和续期,直到锁被释放或没有线程持有锁时,任务才会停止。每次续期成功后,新的定时任务会继续执行,确保锁的有效期在持锁线程存在时不会过期。

因此,虽然定时任务会被创建并执行,但它的执行是基于持锁状态的,只有在锁有效且持有者仍在执行任务的情况下才会持续进行续期。这个设计确保了资源的有效管理,避免不必要的续期操作。

相关文章:

详细分析Redisson分布式锁中的renewExpiration()方法

目录 一、Redisson分布式锁的续期 整体分析 具体步骤和逻辑分析 为什么需要递归调用&#xff1f; 定时任务的生命周期&#xff1f; 一、Redisson分布式锁的续期 Redisson是一个基于Redis的Java分布式锁实现。它允许多个进程或线程之间安全地共享资源。为了实现这一点&…...

实验3,网络地址转换

实验3&#xff1a;网络地址转换 实验目的及要求&#xff1a; 通过实验&#xff0c;掌握NAT技术的工作原理&#xff0c;了解三种不同类型NAT技术的主要作用以及各自的主要应用环境。能够完成静态NAT和复用NAT技术的应用&#xff0c;并熟练掌握NAT技术相关的配置命令。 实验设…...

Java 中的 String 字符串是不可变的

文章目录 什么是不可变字符串&#xff1f;举个例子直观理解 不可变的原理1. 内部实现2. 字符串常量池3. 线程安全 为什么要设计成不可变&#xff1f;什么时候用可变字符串&#xff1f;示例 总结推荐阅读文章 在 Java 编程中&#xff0c;字符串&#xff08;String&#xff09;是…...

计算机网络架构实例

小型企业网络 1. 终端设备&#xff1a; - 员工的台式电脑和笔记本电脑&#xff0c;用于日常办公&#xff0c;如文档处理、邮件收发、业务软件使用等。 - 智能手机和平板电脑&#xff0c;方便员工在外出或移动办公时也能接入公司网络&#xff0c;查看邮件和处理紧急事务。 2.…...

Chrome与Firefox浏览器HTTP自动跳转HTTPS的解决方案

一、背景介绍 随着网络安全意识的不断提高&#xff0c;越来越多的网站开始采用HTTPS协议&#xff0c;以确保数据传输的安全性。然而&#xff0c;有时用户在浏览网页时&#xff0c;可能会遇到HTTP请求被自动跳转至HTTPS的情况导致网站打不开&#xff0c;提示安全问题&#xff0…...

众数信科荣登“2024 CHINA AIGC 100”榜单

2024年10月17日&#xff0c;由非凡产研推出的「2024 CHINA AIGC 100」榜单隆重发布&#xff0c;众数信科凭借领先的企业AI智能体解决方案能力荣登榜单。 非凡产研AIGC 100 评选旨在挖掘国内具有高潜力的AI应用&#xff0c;为AI产业的高质量发展注入新动力。榜单覆盖了教育、医疗…...

【AI知识】距离度量和相似性度量的常见算法

本文介绍一些AI中常见的距离度量和相似性度量算法&#xff1a; 1. 欧几里得距离&#xff08;Euclidean Distance&#xff09; 欧几里得距离是最常见的距离度量方法&#xff0c;用来计算两个向量之间的“直线距离”&#xff0c;也被成为L2范数。 公式如下&#xff0c;其中 x…...

LeetCode1004.最大连续1的个数

题目链接&#xff1a;1004. 最大连续1的个数 III - 力扣&#xff08;LeetCode&#xff09; 1.常规解法&#xff08;会超时&#xff09; 遍历数组&#xff0c;当元素是1时个数加一&#xff0c;当元素是0时且已有的0的个数不超过题目限制时&#xff0c;个数加一&#xff0c;若上…...

Parallels Desktop20虚拟机软件能让你在Mac上无缝运行Windows

Code 生成器&#xff1a;Parallels Desktop 20最新版本虚拟机的奇妙世界 &#x1f31f;【轻松跨越操作系统界限】&#x1f31f; 你是否常常感到在Mac和Windows之间切换太麻烦&#xff1f;Parallels Desktop 20最新版&#xff0c;让你不再为跨系统操作而烦恼。这款虚拟机软件能让…...

Golang | Leetcode Golang题解之第476题数字的补数

题目&#xff1a; 题解&#xff1a; func findComplement(num int) int {highBit : 0for i : 1; i < 30; i {if num < 1<<i {break}highBit i}mask : 1<<(highBit1) - 1return num ^ mask }...

Spring 实现 3 种异步流式接口,干掉接口超时烦恼

大家好&#xff0c;我是小富&#xff5e; 如何处理比较耗时的接口&#xff1f; 这题我熟&#xff0c;直接上异步接口&#xff0c;使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性&#xff0c;处理结果仅返回单个值。在某…...

字节 HLLM 论文阅读

github连接&#xff1a;https://github.com/bytedance/HLLM 探讨问题&#xff1a; 推荐LLM的三个关键问题&#xff1a; LLM预训练权重通常被认为是对世界知识的概括&#xff0c;其对于推荐系统的价值&#xff1f;对推荐任务进行微调的必要性&#xff1f;LLM是否可以在推荐系统…...

Chromium html<iframe>对应c++接口定义

HTML <iframe> 标签 使用 <iframe> 标签 在当前 HTML 文档中嵌入另一个文档&#xff1a; <!DOCTYPE html> <html> <body><h1>iframe 元素</h1><iframe src"https://www.w3school.com.cn" title"W3School 在线教…...

Vue详细入门(语法【三】)

今天滴的学习目标&#xff01;&#xff01;&#xff01; Vue组件是什么&#xff1f;组件的特性和优势Vue3计算属性Vue3监听属性 在前面Vue详细入门&#xff08;语法【一】——【二】&#xff09;当中我们学习了Vue有哪些指令&#xff0c;它的核心语法有哪些&#xff1f;今天我们…...

快速构建SpringBoot项目

快速构建SpringBoot项目 下文将简述如何快速构建一个SpringBoot项目&#xff0c;使用SpringData JPA实现持久层访问&#xff0c;集成lombok、swagger2及集成thymeleaf进行页面展示。 准备环境&#xff1a; JDK版本&#xff1a;jdk17 IntelliJ IDEA版本&#xff1a; 2023.2.7…...

架构设计笔记-14-云原生架构设计理论与实践

知识要点 云原生&#xff08;Cloud Native&#xff09;架构原则&#xff1a; 服务化原则&#xff1a;通过微服务架构&#xff0c;小服务&#xff08;MiniService&#xff09;架构把不同生命周期的模块分离出来&#xff0c;分别进行业务迭代&#xff0c;避免迭代频繁模块被慢速…...

leetcode hot100 之【LeetCode 206. 反转链表】 java实现

LeetCode 206. 反转链表 题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#x…...

基于Spring Cloud的电商系统设计与实现——用户与商品模块的研究(上)

操作系统&#xff1a;Windows Java开发包&#xff1a;JDK1.8 项目管理工具&#xff1a;Maven3.6.0 项目开发工具&#xff1a;IntelliJIDEA 数据库&#xff1a;MySQL Spring Cloud版本&#xff1a;Finchley.SR2 Spring Boot版本&#xff1a;2.0.6.RELEASE 目录 用户模块—user-…...

Spring Boot + Vue 前后端分离项目总结:解决 CORS 和 404 问题

Spring Boot Vue 前后端分离项目总结&#xff1a;解决 CORS 和 404 问题 在进行前后端分离的项目开发中&#xff0c;我们遇到了几个关键问题&#xff1a;跨域问题 (CORS) 和 404 路由匹配错误。以下是这些问题的详细分析和最终的解决方案。 问题描述 跨域请求被阻止 (CORS) 当…...

JVM篇(学习预热 - JVM正式展开 - (实战课程学习总结))(持续更新迭代)

目录 感觉也看了这么多&#xff0c;说一些乱七八糟的内容&#xff0c;完全没有实质的收获&#xff0c;那么现在让我们正式来预热下JVM 吧&#xff1f; 一、程序的执行方式 二、为什么使用 JVM 三、字节码和机器码的区别 四、JDK、JRE与JVM的关系 五、OracleJDK和OpenJDK …...

SkillVLA:通过技能复用应对双-臂操纵中的组合多样性

26年3月来自新加坡国立、北京中关村学院、上海创新研究院、上海AI实验室、上海交大和复旦的论文“SkillVLA: Tackling Combinatorial Diversity in Dual-Arm Manipulation via Skill Reuse”。 视觉-语言-动作&#xff08;VLA&#xff09;模型近期取得的进展&#xff0c;已充分…...

Unity主题系统设计:状态驱动的主题抽象与自动注入方案

1. 这不是换个颜色那么简单&#xff1a;为什么Unity项目里“换肤”总在发布前夜崩盘&#xff1f;你有没有经历过这样的场景&#xff1a;美术同学凌晨两点发来一套新主题资源包&#xff0c;UI设计师说“这次配色更符合品牌调性”&#xff0c;产品说“上线前必须支持深色模式”&a…...

GitLab External Wiki代理权限绕过漏洞深度解析

1. 这个漏洞不是“修个补丁”就能完事的——它暴露的是 GitLab 权限模型里一个被长期忽视的逻辑断层GitLab 安全漏洞 CVE-2025-2614&#xff0c;光看编号容易误以为是又一个常规的越权或 XSS 类型漏洞。但我在实际复现和审计过程中发现&#xff0c;它根本不是配置疏漏或代码拼写…...

PentestGPT实战部署指南:AI驱动的渗透测试工作流落地

1. 这不是另一个“AI安全”的概念玩具&#xff0c;而是一套能真正跑起来的渗透测试辅助工作流“PentestGPT”这个名字刚在GitHub上出现时&#xff0c;我第一反应是点开又关掉——过去三年里&#xff0c;我见过太多打着“AI渗透”旗号的项目&#xff1a;有的只是把ChatGPT API封…...

基于Arduino的智能蓝调节拍器:DIY音乐练习伴侣

1. 项目概述&#xff1a;一个能“演奏”蓝调的低成本节拍器玩乐器的人&#xff0c;对节拍器这东西又爱又恨。它像一位严厉的监工&#xff0c;用单调的“嘀嗒”声强迫你跟上节奏。但你想过没有&#xff0c;这个监工其实可以很有趣&#xff1f;几年前&#xff0c;我在练习蓝调吉他…...

翻译 GDB 官方文档

翻译 GDB 官方文档项目地址官方文档地址下载源码包编译html运行翻译程序项目地址 https://github.com/shootercheng/gdb-translate.git 项目结构 $ tree -L 1 . ├── cmd ├── go.mod ├── input ├── internal ├── LICENSE ├── output ├── README.md ├─…...

Burp Suite拦截与替换机制深度解析:从协议层到规则链

1. 这不是“点开就能用”的功能&#xff0c;而是你和目标系统之间的一道可编程闸门很多人第一次在Burp Suite里点开Proxy → Intercept&#xff0c;看到HTTP请求被拦下来&#xff0c;兴奋地改个User-Agent、删个Cookie就点Forward&#xff0c;以为自己已经掌握了“拦截与替换”…...

【与我学 ClaudeCode】协作篇 之 Worktree + Task Isolation :目录隔离的并行执行通道

作者&#xff1a;逆境不可逃 技术永无止境 希望我的内容可以帮助到你&#xff01;&#xff01;&#xff01;&#xff01; 大家吼 ! 我是 逆境不可逃 今天给大家带来文章《【与我学 ClaudeCode】协作篇 之 Worktree Task Isolation &#xff1a;目录隔离的并行执行通道》. Le…...

MeloTTS实战:多语言语音合成的高效解决方案

MeloTTS实战&#xff1a;多语言语音合成的高效解决方案 【免费下载链接】MeloTTS High-quality multi-lingual text-to-speech library by MyShell.ai. Support English, Spanish, French, Chinese, Japanese and Korean. 项目地址: https://gitcode.com/GitHub_Trending/me/…...

在线文档协作工具选型必看:14款产品对比(2026版)

一、在线文档协作工具的概念解析及其核心功能 在线文档协作工具是基于云端的文档创建、编辑、共享与协同沟通平台&#xff0c;核心目标是让团队在同一份资料上“实时共同工作”&#xff0c;减少反复传文件、版本混乱与沟通成本。 企业常见的核心能力包括&#xff1a; 多人实…...