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

redisson watchdog 原理

目录

  • 1、使用
  • 2、加锁解析
    • 1、getLock
    • 2、tryLock
    • 2.1、当ttl为null时为加锁成功,返回true,否则继续往下执行,判断是否超过等待时间,当前时间减去获取锁前时间就是获取锁花费时间。
    • 2.2、tryAcquire(leaseTime, unit, threadId)
    • 2.3 、renewExpiration
    • 3、总结
  • 3、解锁解析
    • 3.1unlockInnerAsync
    • 3.2cancelExpirationRenewal

1、使用

(1)、添加reddisson的maven依赖

  <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.11.1</version></dependency>

(2)、添加redisson配置

@Configuration
public class RedissonConfig {@Value(value = "${spring.redis.host}")private String host;@Value(value = "${spring.redis.port}")private int port;
//    @Value(value = "${spring.redis.database}")
//    private int database;@Value(value = "${spring.redis.password}")private String password;@Bean(destroyMethod = "shutdown")RedissonClient redisson() {Config config = new Config();//Redis多节点// config.useClusterServers()//     .addNodeAddress("redis://127.0.0.1:6379", "redis://127.0.0.1:7001");//Redis单节点SingleServerConfig singleServerConfig = config.useSingleServer();//可以用"rediss://"来启用SSL连接String address = "redis://" + host + ":" + port;singleServerConfig.setAddress(address);singleServerConfig.setPingConnectionInterval(30*1000);//设置 数据库编号
//        singleServerConfig.setDatabase(database);if(!StringUtil.isEmpty(password)){singleServerConfig.setPassword(password);}//连接池大小:默认值:64// singleServerConfig.setConnectionPoolSize()return Redisson.create(config);}
}

(3)、添加加解锁工具类

@Component
@Slf4j
public class LockUtil {@Autowiredprivate RedissonClient redisson;/*** @description:看门狗分布式锁,统一获取方法* @param lockName 入参* @return boolean* @author zenglingsheng* @date 2024/1/4 16:09**/public boolean getLock(String lockName){boolean lockFlag = true;//使用分布式锁,防止重复提交,避免库存数量错误RLock rLock = redisson.getLock(lockName);try {lockFlag = rLock.tryLock(0, TimeUnit.SECONDS);} catch (Exception e) {log.error("*********Redission分布式锁方法执行异常*********redisKey={}", lockName);}return lockFlag;}/*** @description TODO 统一解锁方法* @param lockKey* @return boolean* @author zenglingsheng* @date 2024/2/2 17:11:25*/public boolean unLock(String lockKey) {try {RLock lock = redisson.getLock(lockKey);//判断锁是否存在,并且判断是否当前线程加的锁if (null != lock && lock.isHeldByCurrentThread()) {lock.unlock();return true;}} catch (Exception e) {log.error(String.format("释放锁%s异常", lockKey));}return false;}}

(4)、添加测试方法

 @RequestMapping("test111")public void doTask(){String lockName="locka";for(int i=0;i<2;i++){int finalI = i;long id =Thread.currentThread().getId();try{System.out.println(finalI);if(!lockUtil.getLock(lockName)){System.out.println(id+":"+finalI+"未获取锁1");return;};System.out.println(id+":"+finalI+"加锁成功");}catch (Exception e){e.printStackTrace();}finally {lockUtil.unLock(lockName);System.out.println(id + ":" + finalI + "释放锁");}}}

2、加锁解析

1、getLock

在这里插入图片描述
getlock时,会新创建RedissonLock对象,其中entryName属性的值为UUID:锁名称。

2、tryLock

   @Overridepublic boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {return tryLock(waitTime, -1, unit);}

当不传leaseTime参数时,leaseTime默认为-1.
在这里插入图片描述

2.1、当ttl为null时为加锁成功,返回true,否则继续往下执行,判断是否超过等待时间,当前时间减去获取锁前时间就是获取锁花费时间。

(1)、time-获取锁花费时间如果小于等于0,说明已经超过等待时间,返回false获取锁失败。
(2)、time-获取锁花费时间如果大于0,说明等待时间未超时,继续往下执行。从代码中可以看到还是执行获取锁继续判断是否等待超时。
在这里插入图片描述

2.2、tryAcquire(leaseTime, unit, threadId)

在这里插入图片描述
从代码中可以看出leaseTime!=-1时执行tryLockInnerAsync使用lua脚本添加redis锁。
在这里插入图片描述
KEYS[1]是锁名称,也是分布式锁的key,ARGV[1]时key的有效时间,ARGV[2]是UUID:线程id。①、第一个if判断是否存在KEYS[1],如果没有则添加hash类型的对象,hash的key是ARGV[2],value是1,然后重新设置KEYS[1]的过期时间,返回nil(就是key对应的vule为空),在java中为null。②、第二个if判断存在KEYS[1]中hash中的ARGV[2]有值时,会hash的value再加1,重新设置KEYS[1]过期时间,返回nil,这种是重入锁的情况,同一个线程可以多次加锁,每次hash中的value加1。③、如果有KEYS[1]但不是同一个线程,会返回当前KEYS[1]的有效时间。
在这里插入图片描述
tryLockInnerAsync中ttlRemaining如果是null时,会进入scheduleExpirationRenewal
在这里插入图片描述
EXPIRATION_RENEWAL_MAP是ConcurrentHashMap,其中中key是entryName(uuid:锁名)
value是ExpirationEntry类型的对象。 如果当前key不存在,会添加当前线程号调用renewExpiration方法,当key存在时,如果线程号已存在会threadIds(LinkedHashMap)的value加1。

2.3 、renewExpiration

  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 " + getName() + " expiration", e);return;}if (res) {// reschedule itselfrenewExpiration();}});}}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);ee.setTimeout(task);}

HashedWheelTimer延时任务(可以参数这个博客)
internalLockLeaseTime / 3=301000/3=101000 单位是毫秒,也就是10秒钟后执行执行renewExpirationAsync,lua脚本对key续期。

在这里插入图片描述
如果当前KEYS1,ARGV2存在的话则重新设置有效时间为30s,返回1,否则返回0,说明锁已经释放了。
在这里插入图片描述
如果lua返回ture,则嵌套renewExpiration方法,10秒钟后继续判断锁释放存在进而是否继续renewExpiration方法。

3、总结

1、reddison中维护了一个ConcurrentHashMap EXPIRATION_RENEWAL_MAP,key是uuid+锁名。value是ExpirationEntry对象,其中threadIds是Map<Long,Integer>,key是线程号,value是线程获取锁的次数。如果是重入锁value会大于1。会有延迟任务开新线程获取threadIds中线程,判断锁有没有释放,没有释放则重置有效时间,继续调用延迟任务,如果释放了则不会执行延迟任务。所以释放锁的重点就是清空EXPIRATION_RENEWAL_MAP的key。

3、解锁解析

在这里插入图片描述
注意解锁时,判断锁是否当前线程。

3.1unlockInnerAsync

在这里插入图片描述
执行lua脚本。①、如果不存在keys[1]的hash的key ARGV[3],执行返回null。
②、如果存在锁会扣减hash中的value。扣减后的value如果大于0则重置有效时间返回false.
③、如果扣减后的value不大于0会删除KEYS[1],并且发布KEYS[2]channelName。
④、如果解锁失败则返回null
在这里插入图片描述

3.2cancelExpirationRenewal

在这里插入图片描述
如果ExpirationEntry中的threadId不为null则,只移除当前线程的threadId。
如果threadId是null或者threadId是空,则移除当前的ExpirationEntry对象,延迟任务不再调用进而不再自动续期,锁被释放。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 深度学习学习经验——循环神经网络(RNN)
  • VMware Workstation Pro for Personal Use (For Windows) 17.0.0
  • rabbitMQ安装与简单demo
  • [数据集][目标检测]航拍屋顶检测数据集VOC+YOLO格式458张3类别
  • python办公自动化:使用`python-docx`根据模板自动化生成文档
  • 【漏洞复现】SuiteCRM responseEntryPoint Sql注入漏洞
  • 【收藏】SaaS企业如何利用KOL营销实现用户增长
  • 【电脑使用耳机录音注意事项】
  • 一文搞定MybatisPlus
  • CentOS 7 部署iscsid 存储服务
  • ffmpeg最新5.1.6版本源码安装
  • 邻接表的具体实例
  • Linux简单介绍(1)
  • CVPR 2024论文分享┆LMDrive:基于大模型的闭环端到端自动驾驶
  • 【Unity3D小技巧】Unity3D中实现对InputField的自定义输入限制实例
  • “大数据应用场景”之隔壁老王(连载四)
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • Android单元测试 - 几个重要问题
  • Angular Elements 及其运作原理
  • export和import的用法总结
  • gops —— Go 程序诊断分析工具
  • interface和setter,getter
  • JavaScript函数式编程(一)
  • JavaScript设计模式之工厂模式
  • Object.assign方法不能实现深复制
  • sessionStorage和localStorage
  • underscore源码剖析之整体架构
  • 基于Mobx的多页面小程序的全局共享状态管理实践
  • 开放才能进步!Angular和Wijmo一起走过的日子
  • 老板让我十分钟上手nx-admin
  • 如何正确配置 Ubuntu 14.04 服务器?
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 责任链模式的两种实现
  • 湖北分布式智能数据采集方法有哪些?
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • ​iOS安全加固方法及实现
  • # Redis 入门到精通(八)-- 服务器配置-redis.conf配置与高级数据类型
  • $Django python中使用redis, django中使用(封装了),redis开启事务(管道)
  • (02)Hive SQL编译成MapReduce任务的过程
  • (6)STL算法之转换
  • (7)STL算法之交换赋值
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (差分)胡桃爱原石
  • (动态规划)5. 最长回文子串 java解决
  • (二)springcloud实战之config配置中心
  • (二)原生js案例之数码时钟计时
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (生成器)yield与(迭代器)generator
  • (原創) 是否该学PetShop将Model和BLL分开? (.NET) (N-Tier) (PetShop) (OO)
  • (转) Android中ViewStub组件使用
  • .NET Core 和 .NET Framework 中的 MEF2
  • .NET Core 中插件式开发实现