Redission分布式锁 watch dog 看门狗机制
为了避免Redis实现的分布式锁超时,Redisson中引入了watch dog的机制,他可以帮助我们在Redisson实例被关闭前,不断的延长锁的有效期。
- 自动续租:当一个Redisson客户端实例获取到一个分布式锁时,如果没有指定锁的超时时间,Watchdog会基于Netty的时间轮启动一个后台任务,定期向Redis发送命令,重新设置锁的过期时间,通常是锁的租约时间的1/3。这确保了即使客户端处理时间较长,所持有的锁也不会过期。
- 每次续期的时长:默认情况下,每10s钟做一次续期,续期时长是30s。
- 停止续期:当锁被释放或者客户端实例被关闭时,Watchdog会自动停止对应锁的续租任务。
💖 底层实现
👨🏫 RedissonBaseLock.renewExpiration()
protected void scheduleExpirationRenewal(long threadId) {// 创建一个新的过期续期条目ExpirationEntry entry = new ExpirationEntry();// 尝试将新的过期续期条目放入到过期续期映射中,如果已存在则不替换ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);if (oldEntry != null) {// 如果已存在,则将线程ID添加到旧的过期续期条目中oldEntry.addThreadId(threadId);} else {// 如果是新的过期续期条目,则添加线程ID,并尝试续期entry.addThreadId(threadId);try {// 尝试续期renewExpiration();} finally {// 如果当前线程被中断,则取消续期if (Thread.currentThread().isInterrupted()) {cancelExpirationRenewal(threadId);}}}
}// 定时任务执行续期
private void renewExpiration() {// 从过期续期映射中获取当前的过期续期条目ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee == null) {// 如果没有找到,则直接返回return;}// 创建一个新的定时任务,用于执行续期逻辑Timeout task = getServiceManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {// 再次检查过期续期条目是否仍然存在ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ent == null) {return;}// 获取线程IDLong threadId = ent.getFirstThreadId();if (threadId == null) {return;}// 使用LUA脚本异步续期CompletionStage<Boolean> future = renewExpirationAsync(threadId);future.whenComplete((res, e) -> {if (e != null) {// 如果有异常发生,记录错误并从映射中移除过期续期条目log.error("Can't update lock {} expiration", getRawName(), e);EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}if (res) {// 如果续期成功,则重新调度续期任务renewExpiration();} else {// 如果续期失败,则取消续期cancelExpirationRenewal(null);}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);// 将定时任务与过期续期条目关联ee.setTimeout(task);
}// 使用LUA脚本,进行续期
protected CompletionStage<Boolean> renewExpirationAsync(long threadId) {// 使用evalWriteAsync方法异步执行LUA脚本,用于续期return evalWriteAsync(getRawName(), 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(getRawName()),internalLockLeaseTime, getLockName(threadId));
}
可以看到,上面的代码的主要逻辑就是用了一个TimerTask来实现了一个定时任务,设置了internalLockLeaseTime / 3的时长进行一次锁续期。默认的超时时长是30s,那么他会每10s进行一次续期,通过LUA脚本进行续期,再续30s
不过,这个续期也不是无脑续,他也是有条件的,其中ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());这个值得我们关注,他会从EXPIRATION_RENEWAL_MAP中尝试获取一个KV对,如果查不到,就不续期了。
EXPIRATION_RENEWAL_MAP这个东西,会在unlock的时候操作的,对他进行remove,所以一个锁如果被解了,那么就不会再继续续期了:
@Override
public void unlock() {try {// 异步执行解锁操作get(unlockAsync(Thread.currentThread().getId()));} catch (RedisException e) {// 检查异常是否是由于线程没有持有锁导致的if (e.getCause() instanceof IllegalMonitorStateException) {// 如果是,则抛出原始的 IllegalMonitorStateException异常throw (IllegalMonitorStateException) e.getCause();} else {// 如果不是,则抛出原始的RedisException异常throw e;}}
}@Override
public RFuture<Void> unlockAsync(long threadId) {// 使用getServiceManager执行解锁操作return getServiceManager().execute(() -> unlockAsync0(threadId));
}private RFuture<Void> unlockAsync0(long threadId) {// 异步执行解锁操作CompletionStage<Boolean> future = unlockInnerAsync(threadId);// 处理异步操作的结果或异常CompletionStage<Void> f = future.handle((opStatus, e) -> {// 取消续期任务cancelExpirationRenewal(threadId);if (e != null) {// 如果有异常发生,抛出CompletionExceptionif (e instanceof CompletionException) {throw (CompletionException) e;}throw new CompletionException(e);}if (opStatus == null) {// 如果解锁操作失败,抛出IllegalMonitorStateExceptionIllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "+ id + " thread-id: " + threadId);throw new CompletionException(cause);}return null;});// 将CompletableFuture包装为RFuturereturn new CompletableFutureWrapper<>(f);
}protected void cancelExpirationRenewal(Long threadId) {// 从过期续期映射中获取过期续期条目ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (task == null) {// 如果没有找到,则直接返回return;}if (threadId != null) {// 如果线程ID不为空,则从过期续期条目中移除该线程IDtask.removeThreadId(threadId);}if (threadId == null || task.hasNoThreads()) {// 如果线程ID为空或者过期续期条目中没有线程ID,则取消定时任务Timeout timeout = task.getTimeout();if (timeout != null) {timeout.cancel();}// 从过期续期映射中移除过期续期条目EXPIRATION_RENEWAL_MAP.remove(getEntryName()); // 取消续期关键点}
}
核心:EXPIRATION_RENEWAL_MAP.remove(getEntryName());
一次unlock过程中,对EXPIRATION_RENEWAL_MAP进行移除,进而取消下一次锁续期的实现细节。
并且在unlockAsync方法中,不管unlockInnerAsync是否执行成功,还是抛了异常,都不影响cancelExpirationRenewal的执行,也可以理解为,只要unlock方法被调用了,即使解锁未成功,那么也可以停止下一次的锁续期。
💖 续期
加锁代码
/*** 尝试异步获取分布式锁。** @param waitTime 等待获取锁的最大时间,如果设置为-1,则表示无限等待。* @param leaseTime 锁的过期时间,如果设置为-1,则表示使用默认的过期时间。* @param unit 时间单位,用于将leaseTime转换为毫秒。* @param threadId 当前线程的唯一标识符。* @return 一个RFuture对象,表示异步操作的结果,如果成功获取锁,则返回剩余的过期时间(毫秒)。* @throws InterruptedException 如果当前线程在等待过程中被中断。*/
private RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {// 尝试获取锁的异步方法RFuture<Long> ttlRemainingFuture;// 如果锁的过期时间大于0,则使用指定的过期时间if (leaseTime > 0) {ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {// 如果锁的过期时间不大于0,则使用内部锁的过期时间ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}// 处理没有同步获取锁的情况CompletionStage<Long> s = handleNoSync(threadId, ttlRemainingFuture);// 将处理后的CompletionStage包装为RFuturettlRemainingFuture = new CompletableFutureWrapper<>(s);// 当ttlRemainingFuture完成时,如果ttlRemaining为null,则表示锁已成功获取CompletionStage<Long> f = ttlRemainingFuture.thenApply(ttlRemaining -> {// 锁已获取if (ttlRemaining == null) {// 如果锁的过期时间大于0,则设置锁的过期时间if (leaseTime > 0) {internalLockLeaseTime = unit.toMillis(leaseTime);} else {// 如果锁的过期时间不大于0,则安排锁的过期时间续期scheduleExpirationRenewal(threadId);}}// 返回ttlRemaining,如果为null,则表示锁已获取return ttlRemaining;});// 将处理后的CompletionStage包装为RFuturereturn new CompletableFutureWrapper<>(f);
}

💖 停止续期
如果一个锁的unlock方法被调用了,那么就会停止续期。
那么,取消续期的核心代码如下:
/*** 取消与锁关联的自动续期任务。** @param threadId 如果不为null,则只取消与特定线程ID关联的续期任务。*/
protected void cancelExpirationRenewal(Long threadId) {// 从过期续期映射中获取当前的过期续期条目ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (task == null) {// 如果没有找到对应的续期条目,则直接返回return;}if (threadId != null) {// 如果提供了线程ID,则从续期条目中移除该线程IDtask.removeThreadId(threadId);}if (threadId == null || task.hasNoThreads()) {// 如果没有提供线程ID,或者续期条目中没有其他线程ID,则取消定时任务Timeout timeout = task.getTimeout();if (timeout != null) {// 取消定时任务timeout.cancel();}// 从过期续期映射中移除过期续期条目EXPIRATION_RENEWAL_MAP.remove(getEntryName());}
}
主要就是通过 EXPIRATION_RENEWAL_MAP.remove来做的。那么cancelExpirationRenewal还有下面一处调用:
protected void scheduleExpirationRenewal(long threadId) {ExpirationEntry entry = new ExpirationEntry();ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);if (oldEntry != null) {oldEntry.addThreadId(threadId);} else {entry.addThreadId(threadId);try {renewExpiration();} finally {if (Thread.currentThread().isInterrupted()) {cancelExpirationRenewal(threadId);}}}
}
也就是说,在尝试开启续期的过程中,如果线程被中断了,那么就会取消续期动作了。
目前,Redisson是没有针对最大续期次数和最大续期时间的支持的。所以,正常情况下,如果没有解锁,是会一直续期下去的。
💖 客户端挂了,锁会不会一直续期?
Redission 是 redis 的客户端

相关文章:
Redission分布式锁 watch dog 看门狗机制
为了避免Redis实现的分布式锁超时,Redisson中引入了watch dog的机制,他可以帮助我们在Redisson实例被关闭前,不断的延长锁的有效期。 自动续租:当一个Redisson客户端实例获取到一个分布式锁时,如果没有指定锁的超时时…...
人脸识别系统架构
目录 1. 系统架构 1.1 采集子系统 1.2 解析子系统 1.3 存储子系统 1.4 比对子系统 1.5 决策子系统 1.6 管理子系统 1.7 应用开放接口 2. 业务流程 2.1 人脸注册 2.2 人脸验证 2.2.1 作用 2.2.2 特点 2.2.3 应用场景 2.3 人脸辨识 2.3.1 作用 2.3.2 特点 2.3.3…...
数塔问题(蛮力算法和动态规划)
题目:如下图是一个数塔,从顶部出发在每一个节点可以选择向左或者向右走,一直走到底层,要求找出一条路径,使得路径上的数字之和最大,及路径情况。(使用蛮力算法和动态规划算法分别实现) #include…...
启动 Redis 服务和连接到 Redis 服务器
启动 Redis 服务和连接到 Redis 服务器的步骤通常依赖于你的操作系统和 Redis 的安装方式。以下是一些常见的步骤: ### 启动 Redis 服务 对于大多数 Linux 发行版,Redis 服务可以通过以下命令启动: 1. 如果 Redis 是通过包管理器安装的&am…...
我独自升级崛起在哪下载 我独自升级电脑PC端下载教程分享
将于5月8日在全球舞台闪亮登场的动作角色扮演游戏《我独自升级崛起》,灵感源自同名热门动画与网络漫画,承诺为充满激情的游戏玩家群体带来一场集深度探索与广阔体验于一身的奇幻旅程。该游戏以独特的网络武侠世界观为基底,展现了一位普通人踏…...
STM32F4xx开发学习—GPIO
GPIO 学习使用STM32F407VET6GPIO外设 寄存器和标准外设库 1. 寄存器 存储器映射 存储器本身是不具有地址的,是一块具有特定功能的内存单元,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就叫做存储区映射。给内存单元分配地址之后…...
引领农业新质生产力,鸿道(Intewell®)操作系统助力农业机器人创新发展
4月27日至29日,2024耒耜国际会议在江苏大学召开。科东软件作为特邀嘉宾出席此次盛会,并为江苏大学-科东软件“农业机器人操作系统”联合实验室揭牌。 校企联合实验室揭牌 在开幕式上,江苏大学、科东软件、上交碳中和动力研究院、遨博智能研究…...
扩展学习|一文读懂知识图谱
一、知识图谱的技术实现流程及相关应用 文献来源:曹倩,赵一鸣.知识图谱的技术实现流程及相关应用[J].情报理论与实践,2015, 38(12):127-132. (一)知识图谱的特征及功能 知识图谱是为了适应新的网络信息环境而产生的一种语义知识组织和服务的方…...
ubuntu中的docker记录(3)——如何安装nvidia-docker以更好地支持GPU加速计算应用程序的运行
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、nvidia-docker2的安装1. 安装docker2. 安装nvidia-docker2(1) 添加密钥(2) 更新软件列表(3) 安装nvidia-docker2(4) 测试nvidia-docker2 二、可能的报错及解…...
MLP实现fashion_mnist数据集分类(1)-模型构建、训练、保存与加载(tensorflow)
1、查看tensorflow版本 import tensorflow as tfprint(Tensorflow Version:{}.format(tf.__version__)) print(tf.config.list_physical_devices())2、fashion_mnist数据集下载与展示 (train_image,train_label),(test_image,test_label) tf.keras.datasets.fashion_mnist.l…...
ChatGPT-税收支持新质生产力
Prompt: 税收发展助力新质生产力 Response: 是的,税收发展可以促进新质生产力的发展。通过税收政策的调整和优化,政府可以提供更好的创新环境,激发企业投资研发,推动新技术、新产品的出现,从而推动经济结构升级和新…...
Linux下深度学习虚拟环境的搭建与模型训练
在深度学习实践中,环境配置是十分重要且免不了的一步。本文以 YOLOv4 模型,介绍在Linux下虚拟环境配置到模型训练的过程。 安装Miniconda: Miniconda是Anaconda的一个轻量级版本,非常适合用于科学计算和数据处理。 wget https:…...
Map-Reduce是个什么东东?
MapReduce是一种用于使用并行分布式算法在集群计算机上处理大型数据集的编程模型及其相关实现。这一概念首先由Google普及,并随后作为Apache Hadoop项目的一部分开源发布。 MapReduce的基本工作流程: 映射(Mapping):这是第一阶段,…...
上位机工作感想-从C#到Qt的转变-2
2.技术总结 语言方面 最大收获就是掌握了C Qt编程,自己也是粗看了一遍《深入理解计算机系统》,大致了解了计算机基本组成、虚拟内存、缓存命中率等基基础知识,那本书确实有的部分看起来很吃力,等这段时间忙完再研读一遍。对于封装…...
【C++】C++ 中 的 lambda 表达式(匿名函数)
C11 引入的匿名函数,通常被称为 Lambda 函数,是语言的一个重要增强,它允许程序员在运行时创建简洁的、一次性使用的函数对象。Lambda 函数的主要特点是它们没有名称,但可以捕获周围作用域中的变量,这使得它们非常适合在…...
OpenSSL实现AES-CBC加解密,可一次性加解密任意长度的明文字符串或字节流(QT C++环境)
本篇博文讲述如何在Qt C的环境中使用OpenSSL实现AES-CBC-Pkcs7加/解密,可以一次性加解密一个任意长度的明文字符串或者字节流,但不适合分段读取加解密的(例如,一个4GB的大型文件需要加解密,要分段读取,每次…...
cURL:命令行下的网络工具
序言 在当今互联网时代,我们经常需要与远程服务器通信,获取数据、发送请求或下载文件。在这些情况下,cURL 是一个强大而灵活的工具,它允许我们通过命令行进行各种类型的网络交互。本文将深入探讨 cURL 的基本用法以及一些高级功能…...
Baumer工业相机堡盟工业相机如何通过NEOAPISDK查询和轮询相机设备事件函数(C#)
Baumer工业相机堡盟工业相机如何通过NEOAPISDK查询和轮询相机设备事件函数(C#) Baumer工业相机Baumer工业相机NEOAPI SDK和相机设备事件的技术背景Baumer工业相机通过NEOAPISDK在相机中查询和轮询相机设备事件函数功能1.引用合适的类文件2.通过NEOAPISDK…...
Day45代码随想录动态规划part07:70. 爬楼梯(进阶版)、322. 零钱兑换、279.完全平方数、139.单词拆分
Day45 动态规划part07 完全背包 70. 爬楼梯(进阶版) 卡码网链接:57. 爬楼梯(第八期模拟笔试) (kamacoder.com) 题意:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬至多m (1 < m < n)个…...
土壤重金属含量分布、Cd镉含量、Cr、Pb、Cu、Zn、As和Hg、土壤采样点、土壤类型分布
土壤是人类赖以生存和发展的重要资源之一,也是陆地生态系统重要的组成部分。近年来, 随着我国城市化进程加快,矿产资源开发、金属加工冶炼、化工生产、污水灌溉以及不合理的化肥农药施用等因素导致重金属在农田土壤中不断富集。重金属作为土壤环境中一种具有潜在危害…...
基于STM32CubeMx与DSP库的实时音频信号FFT频谱分析实战
1. 从零搭建音频频谱分析系统 去年我在做一个智能家居项目时,需要实时监测环境中的特定声音频率。当时尝试了多种方案,最终发现基于STM32的FFT分析是最经济高效的解决方案。整个过程就像搭积木一样有趣,下面我就把完整的搭建过程分享给大家。…...
用Python玩转大疆Tello无人机:从键盘控制到手势飞行的保姆级实战教程
用Python玩转大疆Tello无人机:从键盘控制到手势飞行的保姆级实战教程 当第一次看到大疆Tello无人机在室内灵巧地完成翻滚动作时,我就被这个巴掌大的飞行器彻底征服了。作为一款专为编程教育设计的迷你无人机,Tello不仅具备稳定的飞行性能&…...
桌面3D扫描技术解析:从结构光原理到实战避坑指南
1. 从工业殿堂到桌面工坊:3D扫描的平民化浪潮 几年前,如果你跟人提起3D扫描,脑海里浮现的画面多半是电影特效工作室里,演员身上贴满标记点,被一圈昂贵的专业相机环绕;或者是汽车制造车间里,巨大…...
手把手带你用C语言模拟RISC-V的`li`指令扩展过程(附完整代码)
手把手带你用C语言模拟RISC-V的li指令扩展过程(附完整代码) 在计算机体系结构的学习中,理解指令集的工作原理是掌握底层编程的关键。RISC-V作为一种开源指令集架构,近年来在学术界和工业界都获得了广泛关注。本文将带领读者通过C语…...
利用Taotoken统一管理多个AI项目的API密钥与访问权限
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 利用Taotoken统一管理多个AI项目的API密钥与访问权限 对于同时维护多个AI应用或为不同客户部署服务的开发者与团队而言,…...
如何高效获取金融数据:Python通达信接口的完整指南
如何高效获取金融数据:Python通达信接口的完整指南 【免费下载链接】mootdx 通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx 在金融数据分析和量化交易领域,获取准确、及时且成本可控的市场数据一直…...
Web 安全深入审计检查清单
一、审计准备与范围界定 适用于渗透测试、安全评估及合规审计(如等保、ISO 27001):检查项具体内容授权确认获取书面授权书(RoE),明确测试时间、IP/域名范围、测试深度资产梳理主站、子域、API 端点、CDN、W…...
5分钟掌握Windows窗口置顶神器:PinWin完整使用指南
5分钟掌握Windows窗口置顶神器:PinWin完整使用指南 【免费下载链接】PinWin Pin any window to be always on top of the screen 项目地址: https://gitcode.com/gh_mirrors/pin/PinWin Windows窗口置顶工具PinWin是一款让你彻底告别频繁窗口切换的神器。无论…...
AI系统行为治理:构建确定性护栏与运行时安全控制
1. 项目概述:为AI系统构建确定性的行为护栏如果你正在构建一个会“动手”的AI应用——无论是能帮你写代码的智能助手,还是能操作数据库的自动化流程,甚至是部署在物理设备上的机器人——那么你迟早会面临一个核心问题:如何确保它只…...
【实战复盘】Win11 23H2 微信图片拖拽至抖店失效:跨越注册表修复的降级排障SOP
一、 故障描述与初始环境故障现象:用户无法将微信聊天窗口内的图片,直接拖拽至“抖店工作台”聊天输入框中,系统表现为拖拽操作被拦截或无响应。故障环境:Windows 11 23H2 版本。前置历史:该故障电脑此前拖拽功能正常&…...
