Redisson看门狗机制

一、背景

网上redis分布式锁的工具方法,大都满足互斥、防止死锁的特性,有些工具方法会满足可重入特性。如果只满足上述3种特性会有哪些隐患呢?redis分布式锁无法自动续期,比如,一个锁设置了1分钟超时释放,如果拿到这个锁的线程在一分钟内没有执行完毕,那么这个锁就会被其他线程拿到,可能会导致严重的线上问题。

既然存在锁过期而任务未执行完毕的情况,那是否有一种可以在任务未完成时自动续期的机制呢,几年前在redisson中找到了看门狗的自动续期机制,就是解决这种分布式锁自动续期的问题的。
在这里插入图片描述
Redisson 锁的加锁机制如上图所示,线程去获取锁,获取成功则执行lua脚本,保存数据到redis数据库。如果获取失败: 一直通过while循环尝试获取锁(可自定义等待时间,超时后返回失败),获取成功后,执行lua脚本,保存数据到redis数据库。Redisson提供的分布式锁是支持锁自动续期的,也就是说,如果线程仍旧没有执行完,那么redisson会自动给redis中的目标key延长超时时间,这在Redisson中称之为 Watch Dog 机制

二、redisson 看门狗使用以及原理

1.redisson配置和初始化

pom.xml

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.16.4</version>
</dependency>

application.yaml

redis:host: xxxxxxxpassword: xxxxxxmax-active: 8max-idle: 500max-wait: 1min-idle: 0port: 6379timeout: 1000msdatabase: 0

redisson配置类

@Configuration
public class RedisConfig {//最简单的redisson初始化配置@Beanpublic RedissonClient getRedisson() {Config config = new Config();config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);return Redisson.create(config);}
}

2.redisson看门狗使用

使用redisson分布式锁的目的主要是防止分布式应用产生的并发问题,所以一般会进行一下调整改为AOP形式去进行业务代码解耦。这里会加入自定义注解和AOP。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {//锁的名称String lockName();//锁的失效时间long leaseTime() default 3;//是否开启看门狗,默认开启,开启时锁的失效时间不执行。任务未完成时会自动续期锁时间//使用看门狗,锁默认redis失效时间未30秒。失效时间剩余1/3时进行续期判断,是否需要续期boolean watchdog() default true;
}
public class RedisLockAspect {@Autowiredprivate RedissonClient redissonClient;private static final String REDIS_PREFIX = "redisson_lock:";@Around("@annotation(redisLock)")public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {String lockName = redisLock.lockName();RLock rLock = redissonClient.getLock(REDIS_PREFIX + lockName);Object result = null;boolean isLock;if(redisLock.watchdog()){isLock =rLock.tryLock(0, TimeUnit.SECONDS);}else {isLock =rLock.tryLock(0,redisLock.leaseTime(), TimeUnit.SECONDS);}if(isLock){try {//执行方法result = joinPoint.proceed();} finally {if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {rLock.unlock();}}}else {log.warn("The lock has been taken:{}",REDIS_PREFIX + lockName);}return result;}
}
@Scheduled(cron = "*/10 * * * * ?")//使用注解进行加锁@RedisLock(lockName = "npa_lock_test",watchdog = true)public void redisLockTest() {System.out.println("get lock and perform a task");try {Thread.sleep(20000L);} catch (InterruptedException e) {e.printStackTrace();}}

这里使用定时任务进行模拟调用,10秒一次定时任务请求,线程执行睡眠20秒后完成。下面看一下执行结果。当获取锁后,第二次定时任务执行时。锁未被释放。所以失败,第三次获取时所已经释放,所以成功。
如果拿到分布式锁的节点宕机,且这个锁正好处于锁住的状态时,会出现锁死的状态,为了避免这种情况的发生,锁都会设置一个过期时间。这样也存在一个问题,加入一个线程拿到了锁设置了30s超时,在30s后这个线程还没有执行完毕,锁超时释放了,就会导致问题,Redisson给出了自己的答案,就是 watch dog 自动延期机制。
Redisson提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期,也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断的延长锁超时时间,锁不会因为超时而被释放。
默认情况下,看门狗的续期时间是30s,也可以通过修改Config.lockWatchdogTimeout来另行指定。另外Redisson 还提供了可以指定leaseTime参数的加锁方法来指定加锁的时间。超过这个时间后锁便自动解开了,不会延长锁的有效期。

3.redisson源码

Redisson的源码版本基于:3.16.4,同时需要注意的是:
watchDog 只有在未显示指定加锁时间(leaseTime)时才会生效。(这点很重要)
lockWatchdogTimeout设定的时间不要太小 ,比如我之前设置的是 100毫秒,由于网络直接导致加锁完后,watchdog去延期时,这个key在redis中已经被删除了。
在调用lock方法时,会最终调用到tryAcquireAsync。调用链为:lock()->tryAcquire->tryAcquireAsync,详细解释如下:

使用了RFuture(相关内容涉及Netty异步回调模式-Future和Promise剖析)去启动异步线程执行

private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {RFuture<Long> ttlRemainingFuture;//如果指定了加锁时间,会直接去加锁if (leaseTime != -1) {ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {//没有指定加锁时间 会先进行加锁,并且默认时间就是 LockWatchdogTimeout的时间//这个是异步操作 返回RFuture 类似netty中的futurettlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);}//这里也是类似netty Future 的addListener,在future内容执行完成后执行ttlRemainingFuture.onComplete((ttlRemaining, e) -> {if (e != null) {return;}// lock acquiredif (ttlRemaining == null) {// leaseTime不为-1时,不会自动延期if (leaseTime != -1) {internalLockLeaseTime = unit.toMillis(leaseTime);} else {//这里是定时执行 当前锁自动延期的动作,leaseTime为-1时,才会自动延期scheduleExpirationRenewal(threadId);}}});return ttlRemainingFuture;}

scheduleExpirationRenewal 中会调用renewExpiration。 这里我们可以看到是启用一个timeout定时,去执行延期动作,

private void renewExpiration() {ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee == null) {return;}Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {@Overridepublic void run(Timeout timeout) throws Exception {ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ent == null) {return;}Long threadId = ent.getFirstThreadId();if (threadId == null) {return;}RFuture<Boolean> future = renewExpirationAsync(threadId);future.onComplete((res, e) -> {if (e != null) {log.error("Can't update lock " + getRawName() + " expiration", e);EXPIRATION_RENEWAL_MAP.remove(getEntryName());return;}if (res) {//如果 没有报错,就再次定时延期// reschedule itselfrenewExpiration();} else {cancelExpirationRenewal(null);}});}// 这里我们可以看到定时任务 是 lockWatchdogTimeout 的1/3时间去执行 renewExpirationAsync}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);}
protected RFuture<Boolean> renewExpirationAsync(long threadId) {return this.evalWriteAsync(this.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(this.getRawName()), this.internalLockLeaseTime, this.getLockName(threadId));}

结论

  • watch dog 在当前节点存活时每10s给分布式锁的key续期 30s;
  • watch dog 机制启动,且代码中没有释放锁操作时,watch dog 会不断的给锁续期;
  • 如果程序释放锁操作时因为异常没有被执行,那么锁无法被释放,所以释放锁操作一定要放到 finally {} 中;
  • 要使 watchLog机制生效 ,lock时 不要设置 过期时间
  • watchlog的延时时间 可以由 lockWatchdogTimeout指定默认延时时间,但是不要设置太小。如100
  • watchdog 会每 lockWatchdogTimeout/3时间,去延时。
  • watchdog 通过 类似netty的 Future功能来实现异步延时
  • watchdog 最终还是通过 lua脚本来进行延时

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

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

相关文章

YOLOv5白皮书-第Y3周:yolov5s.yaml文件解读

YOLOv5白皮书-第Y3周:yolov5s.yaml文件解读 YOLOv5白皮书-第Y3周:yolov5s.yaml文件解读一、前言二、我的环境三、yolov5s.yaml源文件内容四、Parameters五、anchors配置六、backbone七、head八、总结 OLOv5-第Y2周&#xff1a;训练自己的数据集) YOLOv5白皮书-第Y3周:yolov5s.…

从MySQL到TiDB:兼容性全解析

MySQL 在高并发和大数据量场景下&#xff0c;单个实例的扩展性有限。而 TiDB 作为一款分布式NewSQL数据库&#xff0c;设计之初就支持水平扩展&#xff08;Scale-Out&#xff09;&#xff0c;通过增加节点来线性提升处理能力和存储容量&#xff0c;能够很好地应对大规模数据和高…

机器学习——绪论总结

目录 一、引入 二、基本术语 三、假设空间与归纳偏 四、模型选择 一、引入 机器学习&#xff1a;通过计算手段&#xff0c;得出具有能够自我修改、完善能力的模型&#xff0c;利用经验改善系统自身性能。算法使用数据得到模型的过程即称为学习&#xff0c;或训练 流程&…

Framework - ActivityThread 应用启动UI渲染流程

一、概念 ActivityThread拥有 main(String[] agrs) 方法&#xff0c;作为程序的入口&#xff0c;是应用程序的初始化类。&#xff08;ActivityThread不是主线程&#xff0c;它在 main() 方法中实例化&#xff0c;是运行在主线程中。&#xff09;ApplicationThread是 ActivityT…

【Nginx】Ubuntu如何安装使用Nginx反向代理?

文章目录 使用Nginx反向代理2个web接口服务步骤 1&#xff1a;安装 Nginx步骤 2&#xff1a;启动 Nginx 服务步骤 3&#xff1a;配置 Nginx步骤 4&#xff1a;启用配置步骤 5&#xff1a;检查配置步骤 6&#xff1a;重启 Nginx步骤 7&#xff1a;访问网站 proxy_set_header 含义…

海外IP代理:解锁网络边界的实战利器

文章目录 引言&#xff1a;正文&#xff1a;一、Roxlabs全球IP代理服务概览特点&#xff1a;覆盖范围&#xff1a;住宅IP真实性&#xff1a;性价比&#xff1a;在网络数据采集中的重要性&#xff1a; 二、实战应用案例一&#xff1a;跨境电商竞品分析步骤介绍&#xff1a;代码示…

LeetCode--189

189. 轮转数组 提示 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转…

Ingress

文章目录 环境准备什么是 Ingress认识 Ingress 资源Ingress 控制器(controller)Ingress 规则pathType 路径类型多重匹配Ingress 类TLS生成证书创建密钥 环境准备 下面的 yaml 文件内容&#xff0c;是使用 sts 创建两个 web 服务&#xff0c;并配置对应的 servcie。web 服务的首…

微信小程序(三十三)promise异步写法

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.promise异步与普通异步的写法区别 2.promise异步的优势 源码&#xff1a; index.wxml <view class"preview" bind:tap"onChoose"><image src"{{avatar}}" mode"…

网络时间协议NTP工作模式

单播服务器/客户端模式 单播服务器/客户端模式运行在同步子网中层数较高层上。这种模式下,需要预先知道服务器的IP地址。 客户端:运行在客户端模式的主机(简称客户端)定期向服务器端发送报文,报文中的Mode字段设置为3(客户端模式)。当客户端接收到应答报文时,客户端会…

Chapter One - The History of Computers

Chapter One - The History of Computers 第一章 - 计算机的历史 I. Reading Material I. 阅读材料 My friends, let’s embark on an enlightening journey through the captivating history of computers, unraveling the intricate threads that have woven the technolog…

DockerCompose+SpringBoot+Nginx+Mysql实践

DockerComposeSpringBootNginxMysql实践 1、Spring Boot案例 首先我们先准备一个 Spring Boot 使用 Mysql 的小场景&#xff0c;我们做这样一个示例&#xff0c;使用 Spring Boot 做一个 Web 应 用&#xff0c;提供一个按照 IP 地址统计访问次数的方法&#xff0c;每次请求时…

linux交叉编译方法——虚拟机编译,在树莓派平台上运行

一、 交叉编译是什么 交叉编译 是在一个平台上生成另一个平台上的可执行代码。 我们再windows上面编写C51代码&#xff0c;并编译成可执行代码&#xff0c;如xx.hex, 是在c51上面运行&#xff0c;不是在windows上面运行 我们在ubuntu上面编写树…

Kubernetes k8s

Kubernetes k8s 一个开源的容器编排引擎&#xff0c;用来对容器化应用进行自动化部署、 扩缩和管理。 从架构设计层面&#xff0c;k8s能很好的解决可用性&#xff0c;伸缩性&#xff1b;从部署运维层面&#xff0c;服务部署&#xff0c;服务监控&#xff0c;应用扩容和故障处…

springboot并mybatis入门启动

pom.xml,需要留意jdk的版本&#xff08;11&#xff09;和springboot版本要匹配&#xff08;2.7.4&#xff09;&#xff0c;然后还要注意mybatis启动l类的版本&#xff08;2.2.2&#xff09; <?xml version"1.0" encoding"UTF-8"?> <project xm…

MAX31865读取PT100/PT1000电阻值

1、芯片介绍 MAX31865是简单易用的热敏电阻至数字输出转换器,优化用于铂电阻温度检测器(RTD)。外部电阻设置RTD灵敏度,高精度Δ- Σ ADC将RTD电阻与基准电阻之比转换为数字输出。MAX31865输入具有高达45V的过压保护,提供可配置的RTD及电缆开路、短路条件检测。 2、芯片特点…

解决Docker打包Eureka注册中心,其他服务无法注册问题

​前言 本文主要是介绍利用docker打包Eureka注册中心&#xff0c;并且发布镜像到服务器&#xff0c;遇到的一个比较坑的问题。主要是服务镜像部署完毕之后&#xff0c;docker容器都能启动&#xff0c;并且也能访问&#xff0c;但是其他服务就是无法注册到注册中心。排除问题&a…

查看阿里云maven仓中某个库有哪些版本

起因 最近项目上有做视频业务&#xff0c;方案是使用阿里云的短视频服务&#xff0c;其中也有使用到阿里云的上传SDK&#xff0c;过程中有遇一个上传SDK的内部崩溃&#xff0c;崩溃栈如下&#xff1a; Back traces starts. java.lang.NullPointerException: Attempt to invok…

只用一台服务器部署上线(宝塔面板) 前后端+数据库

所需材料 工具&#xff1a;安装宝塔面板服务器至少一台、域名一个 前端&#xff1a;生成dist文件&#xff08;前端运行build命令&#xff09; 后端&#xff1a;生成jar包&#xff08;maven运行package命令&#xff09; 准备&#xff1a; 打开宝塔面板&#xff0c;点击进入软…

2、安全开发-Python-Socket编程端口探针域名爆破反弹Shell编码免杀

用途&#xff1a;个人学习笔记&#xff0c;欢迎指正&#xff01; 目录 主要内容&#xff1a; 一、端口扫描(未开防火墙情况) 1、Python关键代码: 2、完整代码&#xff1a;多线程配合Queue进行全端口扫描 二、子域名扫描 三、客户端&#xff0c;服务端Socket编程通信cmd命…