Redis实战案例18-Redisson的锁重试和watchdog机制

news2024/11/26 13:30:27

1. 锁重试

首先要理解为什么要进行锁重试,之前我们在获取锁时,只要一次获取失败就直接返回false,这样的机制需要进行修改;

尝试获取锁的底层逻辑是
在这里插入图片描述
返回锁的有效期(null或者其他值);
为null然后判断是否给leaseTime赋值,若未赋值则自动为-1(赋值为-1同样不开启看门狗),开启看门狗机制;
不为null,则判断剩余等待时间(time)是否大于0,如果大于0则开启订阅,一直dowhile循环获取锁,并且期间不断更新等待时间,如果最后time<0,超时则取消订阅并且返回false无法获取锁;
如果小于0,则直接返回false;

释放锁的顶层逻辑,判断是否成功,失败记录日志即可,成功则要发送释放锁的信号(订阅信号),并且取消看门狗机制,避免一直更新;

在这里插入图片描述

leaseTime可给可不给,不给会直接赋默认值;
一旦给waitTime值,就不会获取失败就返回了,而是在等待时间内不断去尝试获取锁,具体获取的原理如下;

在这里插入图片描述

进入获取锁的业务逻辑

在这里插入图片描述

尝试获取锁

private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    return (Long)this.get(this.tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}

如果 leaseTime 等于 -1,表示没有指定具体的持有时间。此时,方法会继续调用 tryLockInnerAsync() 方法,指定一个默认的锁监视超时时间(lockWatchdogTimeout)作为 leaseTime 的值,并将时间单位调整为毫秒。然后,得到返回的 RFuture 对象 ttlRemainingFuture。

private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    if (leaseTime != -1L) {
        return this.tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    } else {
        RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            if (e == null) {
                if (ttlRemaining == null) {
                    this.scheduleExpirationRenewal(threadId);
                }
            }
        });
        return ttlRemainingFuture;
    }
}

leaseTime未设置默认-1,走else逻辑;
具体给的超时时间为this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout()
也即是30秒;

在这里插入图片描述
在这里插入图片描述

然后进入tryLockInnerAsync()方法获取锁的信息,两种结果:一是返回null,二是返回锁的有效期

执行Luna脚本;
判断锁是否存在;
不存在记录锁的标识并且次数加1;
设置锁的有效期;
如果存在判断锁标识是否相同;
是则锁次数+1,并且设置锁的有效期;

获取锁的操作中,获取成功则返回nil(null),失败则返回锁的有效期;

在这里插入图片描述

这里的get方法就是阻塞等待tryAcquireAsync()的剩余有效期(null或者有效期)

private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    return (Long)this.get(this.tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}

最后返回至最初尝试获取锁位置
两种情况,第一种null,获取成功;
第二种剩余有效期,获取锁失败,再次开始获取

在这里插入图片描述

获取time(获取锁消耗的时间),当waitTime时间大于消耗时间时继续逻辑;
但是此时并不会立马去再去获取锁,因为此时大概率再次获取还是失败,可能使用锁的业务还没有释放锁;

这里给出了一个订阅的概念
代码重新获取当前时间 current,然后创建一个用于订阅锁释放通知的 subscribeFuture,然后调用 subscribe() 方法进行订阅
接下来,代码调用 subscribeFuture.await(time, TimeUnit.MILLISECONDS) 方法,等待 time 毫秒时间,等待订阅结果。
如果在等待期间未收到订阅结果,表示等待超时。在等待超时后,代码会尝试取消订阅任务。如果取消失败,会在 subscribeFuture.onComplete() 方法中进行处理,判断是否需要取消订阅,并调用 unsubscribe() 方法进行处理。
如果取消成功,则代码调用 acquireFailed() 方法进行处理,表示当前线程获取锁失败,最终返回 false。

在这里插入图片描述

订阅的通知就是释放锁Luna脚本当中发布的通知,然后等待订阅结果,等待的时间就是time(锁的最大剩余时间)

在这里插入图片描述

如果是在time时间之内获得释放锁的通知,则会走以下代码;
这段代码是 Redisson 中分布式锁获取的核心逻辑,实现了在等待获取锁的过程中对剩余时间的动态调整。下面是代码的解析:

time是最大剩余时间

  • 首先,代码获取当前时间 current 并计算剩余等待时间 time

  • 如果 time 小于等于 0,表示等待锁的时间已经超过了剩余过期时间,或者锁的剩余过期时间非法。在这种情况下,会调用 acquireFailed() 方法进行处理,并返回 false

  • 如果 time 大于 0,表示还有剩余的时间需要进行等待,也即就是还能去获取锁。代码会进入一个 do-while 循环,并不断尝试获取锁。

  • 在循环中,代码会再次获取当前时间 currentTime,然后调用 tryAcquire() 方法尝试获得锁,同时再次获取返回的锁的剩余过期时间 ttl

  • 如果锁的剩余过期时间 ttl 为空,表示成功获取到锁,会返回 true

  • 如果剩余过期时间 ttl 大于等于 0 且小于等于剩余等待时间 time,表示还有足够的时间可以等待,代码会使用 tryAcquire() 方法返回的 ttl 来等待锁的释放

  • 如果剩余过期时间 ttl 大于剩余等待时间 time,表示还需要等待更长的时间,代码会使用 time 来等待锁的释放

  • 在循环中,每次等待之后,会重新计算剩余等待时间 time

  • 当剩余等待时间 time 小于或等于 0,表示等待超时,会调用 acquireFailed() 方法进行处理,并返回 false

  • 最后,无论等待成功还是失败,都会调用 unsubscribe() 方法取消订阅并释放资源。

这段代码实现了在等待获取锁的过程中对剩余时间的动态调整,确保在等待过程中可以根据实际情况调整等待时间,提高获取锁的效率(不是盲等,而是在不断地尝试)。同时,使用 try-finally 语句块确保在获取锁过程中发生异常时可以正确地取消订阅并释放资源。

try {
    time -= System.currentTimeMillis() - current;
    if (time <= 0L) {
        this.acquireFailed(waitTime, unit, threadId);
        boolean var20 = false;
        return var20;
    } else {
        boolean var16;
        do {
            long currentTime = System.currentTimeMillis();
            ttl = this.tryAcquire(waitTime, leaseTime, unit, threadId);
            if (ttl == null) {
                var16 = true;
                return var16;
            }
            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0L) {
                this.acquireFailed(waitTime, unit, threadId);
                var16 = false;
                return var16;
            }
            currentTime = System.currentTimeMillis();
            if (ttl >= 0L && ttl < time) {
                ((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
            } else {
                ((RedissonLockEntry)subscribeFuture.getNow()).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
            }
            time -= System.currentTimeMillis() - currentTime;
        } while(time > 0L);
        this.acquireFailed(waitTime, unit, threadId);
        var16 = false;
        return var16;
    }
} finally {
    this.unsubscribe(subscribeFuture, threadId);
}

所以只要给waitTime时间,就可以做到锁重试的机制;

2. watchdog机制

目的就是解决业务因设置了超时时间但是在时间内业务并没有执行完的问题,采用看门狗机制进行自动延期机制。
这里有一个大前提,为什么需要设置过期时间?原因在之前的笔记提到过,避免线程1阻塞不释放锁,导致线程2无法获取锁。

如果业务是因为阻塞导致锁释放,也就是ttl为null,然后我们获取到锁,这就出现了安全问题,因为我们必须确定业务是正常执行释放而不是阻塞释放;
在获取分布式锁时,会指定一个锁的看门狗超时时间,即 lockWatchdogTimeout。该超时时间一般会设置为一个比较长的值,通常是锁的最大超时时间的一小部分。
当某个线程成功获取了分布式锁时,Redisson 会在服务端设置一个后台线程,该线程会定期发送续约请求给 Redis,以更新锁的过期时间。同时,该线程会监控当前线程是否仍然持有锁。
如果看门狗超时时间内,持有锁的线程没有发送续约请求,或者没有收到持有锁的线程的心跳信号,则看门狗线程会认为持有锁的线程已经不再活跃或发生了异常,并自动终止该锁,并将锁资源释放。

  • 异步获取锁后,代码会在 ttlRemainingFuture.onComplete() 方法中定义一个回调函数(tryLockInnerAsync()方法),在获取结果后进行处理。

  • 如果回调函数中异常参数 e 为 null,表示获取结果成功。在此情况下,代码判断返回的 ttlRemaining 是否为 null,如果为 null,表示锁的状态为有效,会调用 scheduleExpirationRenewal() 方法进行锁的过期续约。

在这里插入图片描述

而锁的自动续约的实现scheduleExpirationRenewal()

在这里插入图片描述

EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry)方法将这个新的Entry对象放入一个名为EXPIRATION_RENEWAL_MAP的映射中,使用this.getEntryName()方法返回一个唯一的名称(可以理解为锁的名称)作为键。

putIfAbesent()如果不存在再往里面写,返回null;如果是重入,也就是已经存在与当前键关联的Entry对象,在这种情况下,将当前线程ID添加到旧的Entry对象中。这样就保证了线程的唯一,实现可重入唯一。

如果是第一次进入的线程,则把当前线程ID添加到新的Entry对象中,并且调用this.renewExpiration()方法。这个方法用于更新过期时间。

结论:
watchdog每10秒检查一次,续期时间为30秒。当程序没有显式释放锁的操作时,watchdog会不断地执行续期操作,确保分布式锁的key不会过期。这样可以避免其他节点认为锁已经过期而尝试获取锁。
要想看门狗机制启动,不能传leaseTime参数。
锁重试和waitTime参数挂钩,看门狗机制和leaseTime参数挂钩。

假设释放锁的操作异常了,是否一直续约?
在这里插入图片描述

并不会,EXPIRATION_RENEWAL_MAP中的目标 ExpirationEntry 对象已经被移除了,watch dog 通过判断后就不会继续给锁续期了;
所以释放锁的逻辑一定要放在finally体中;

// 获取当前这把锁的任务
EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ent == null) {
    return;
}

在这里插入图片描述

总结

可重入内容

在这里插入图片描述

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

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

相关文章

大数据第一步-Mac安装Hadoop3

1.安装工作 1.1 准备工作 前提是把jJDK8安装好&#xff0c;hadoop3.x最低需要jdk8。 然后打开共享把远程登陆打开&#xff0c;不打开说是后面会报错&#xff0c; 到终端输入命令&#xff1a;ssh localhost 生成新的keygen否则后面会报错 Permission denied 命令&#xff1a;…

SAP/ABAP(二)

一、循环结构 *&---------------------------------------------------------------------* *& Report ZDEMO_LIMING01 *&---------------------------------------------------------------------* *&作者&#xff1a;黎明 *&--------------------------…

DBISAM Client-Server Crack

您是否需要经过验证且可靠的 BDE&#xff08;Borland 数据库引擎&#xff09;替代品&#xff1f; DBISAM是Delphi或CBuilder应用中替代BDE的标准。它已被部署到全球数千个地点&#xff0c;并且可以打上品牌&#xff0c;以至于没有人知道正在使用DBISAM。它是按开发人员许可的&a…

创建型模式 - 工厂模式

概述 需求&#xff1a;设计一个咖啡店点餐系统。 设计一个咖啡类&#xff08;Coffee&#xff09;&#xff0c;并定义其两个子类&#xff08;美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】&#xff09;&#xff1b;再设计一个咖啡店类&#xff08;CoffeeStore&#xf…

Sa-Token + SpringBoot 实现登录鉴权

1. 技术选型 今天最近在做登录、授权的功能,一开始考虑到的是spring boot + spring security,但spring security太重,而我们是轻量级的项目,所以,spring security不适合我们。 而后考虑spring boot + shiro,但shiro自带的aop会影响spring boot的aop,所以,shiro也不适…

等保测评包过是真的吗?安全吗?

最近有小伙伴在问&#xff0c;等保测评包过是真的吗&#xff1f;安全吗&#xff1f;哪位大哥来解答一下&#xff1f; 等保测评包过是真的吗&#xff1f;安全吗&#xff1f; 【回答】&#xff1a;等级保护采用备案与测评机制&#xff0c;而非认证机制&#xff0c;因此不存在“包…

uni-app:常见组件view、text、icon

根据html&#xff1a;可知div是块级标签&#xff0c;span是行级标签 这里view类似于div&#xff0c;text类似于span&#xff0c;即 块级标签&#xff1a;view 行级标签&#xff1a;text、icon 类似效果 两个icon图标&#xff0c;置于第一排 两个view&#xff0c;分别位于第…

四维轻云地理空间数据在线协作管理平台为测绘行业用户解决了这些难题

测绘作为一个基础性行业&#xff0c;从大比例的地形图到铁路网、公路网的分布&#xff0c;再到互联网地图&#xff0c;测绘的身影随处可见。随着科技的不断发展与进步&#xff0c;无人机也成为测绘行业的一部分。通过无人机测绘技术能够获取高精度的影像数据并生成三维模型和点…

华菱电子冲刺创业板上市:计划募资6.5亿元,总经理为日本国籍

7月17日&#xff0c;深圳证券交易所披露的信息显示&#xff0c;已对山东华菱电子股份有限公司&#xff08;下称“华菱电子”&#xff09;发出第1轮审核问询函。目前&#xff0c;华菱电子在深圳证券交易所的审核状态已经变更为“已问询”。 根据公开信息&#xff0c;华菱电子于2…

WooCommerce适合企业电子商务吗?

目录 成功开展电子商务业务变得比以往任何时候都容易。市场上有几个现成的平台&#xff0c;完全有可能将一个初步的想法快速转变为在线贸易业务&#xff0c;并源源不断地收到订单。 什么是 WooCommerce&#xff1f; 为什么您应该考虑使用 WooCommerce 很灵活 重量轻且功…

一文读懂 MySQL 中的索引

文章目录 1. 索引概述1.1 索引概述1.2 优点1.3 缺点1.6 常见索引概念1.6.1 聚簇索引1.6.2 二级索引&#xff08;辅助索引、非聚簇索引&#xff09;1.6.3 联合索引 1.8 MyISAM索引的原理1.9 MyISAM 与 InnoDB对比1.10 索引的代价 2. 索引的创建与设计原则2.1 索引的声明与使用2.…

算法训练营第四十二天||● 01背包问题,你该了解这些! ● 01背包问题,你该了解这些! 滚动数组 ● 416. 分割等和子集

● 01背包问题&#xff0c;你该了解这些&#xff01; 二维dp数组 ● 01背包问题&#xff0c;你该了解这些&#xff01; 滚动数组 一维dp数组 dp[j] max(dp[j], dp[j - weight[i]] value[i]); ● 416. 分割等和子集 本题属于01背包问题的应用&#xff0c; 这道题目是要…

【EasyExcel】在SpringBoot+VUE项目中引入EasyExcel实现对数据的导出(封装工具类)

在SpringBootVUE项目中引入EasyExcel实现导入导出 一、引入EasyExcel 通过maven引入&#xff0c;坐标如下&#xff1a; <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel-core</artifactId><version>3.3.2</version…

Mysql 单表使用索引注意事项(避免失效)

Mysql 单表使用索引 1.尽量建全索引 查询的字段按照顺序在索引中都可以匹配到&#xff01; SQL中查询字段的顺序&#xff0c;跟使用索引中字段的顺序是有关系的。但是在不影响SQL执行结果的前提下&#xff0c;给你自动地优化。没有顺序限制了 2. 最佳左前缀法则 查询字段与…

ES系列--es进阶

一、系统架构 一个运行中的 Elasticsearch 实例称为一个节点&#xff0c;而集群是由一个或者多个拥有相同 cluster.name 配置的节点组成&#xff0c; 它们共同承担数据和负载的压力。当有节点加入集群中或者 从集群中移除节点时&#xff0c;集群将会重新平均分布所有的数据。 …

uniapp离线引入阿里巴巴图标

阿里巴巴图标地址 1.添加图标到购物车 2.点击购物车进入项目 3.下载到本地 4.解压后文件目录 5.放入项目目录中(比如说我经常放在common或者static下icon中) 6.在main.ts或者main.js中引入&#xff08;注意路径&#xff0c;用相对的也行&#xff09; import /static/iconfon…

qt和vue交互

1、首先在vue项目中引入qwebchannel /******************************************************************************** Copyright (C) 2016 The Qt Company Ltd.** Copyright (C) 2016 Klarlvdalens Datakonsult AB, a KDAB Group company, infokdab.com, author Milian …

Cisco学习笔记(CCNA)——Introduction to TCP/IP

Introduction to TCP/IP 常见协议 应用层协议 协议 端口号 描述 HTTP 80 超文本传输协议&#xff0c;提供浏览网页服务 Telnet 23 远程登录协议&#xff0c;提供远程管理服务 FTP 20、21 文件传输协议&#xff0c;提供互联网文件资源共享服务 SMTP 25 简单邮件传…

【个人笔记】Linux登录时要执行的文件与内建命令

Linux登录时要执行的文件 在刚登录Linux时&#xff0c;首先启动 /etc/profile 文件&#xff0c;然后再启动用户目录下的 ~/.bash_profile、 ~/.bash_login或 **/.profile**文件中的其中一个&#xff0c;执行的顺序为&#xff1a;/.bash_profile、 ~/.bash_login、 ~/.profile。…

使用基于自动化生成式 AI 的 Ansible-Lightspeed 服务高效开发 Ansible Playbook(附视频)

《OpenShift / RHEL / DevSecOps 汇总目录》 自动化生成式 AI 的 Ansible-Lightspeed 服务核心功能 Ansible-Lightspeed 是 RedHat 提供的一项自动化生成式 AI 的服务&#xff0c;它可以帮助 Ansible 开发人员更快、更好地开发 Playbook。除了自动生成 Playbook 内容外&#…