Redisson分布式锁源码解析

一、使用Redisson步骤

Redisson各个锁基本所用Redisson各个锁基本所用Redisson各个锁基本所用

二、源码解析

lock锁

1) 基本思想:

lock有两种方法 一种是空参  另一种是带参
         * 空参方法:会默认调用看门狗的过期时间30*1000(30秒)
         * 然后在正常运行的时候,会启用定时任务调用重置时间的方法(间隔为开门看配置的默认过期时间的三分之一,也就是10秒)
         * 当出现错误的时候就会停止续期,直到到期释放锁或手动释放锁
         * 带参方法:手动设置解锁时间,到期后自动解锁,或者业务完成后手动解锁,不会自动续期

源码:

Lock

调用lockInterruptibly()方法会默认传入lease 为-1,该值再后面起作用

  public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {long threadId = Thread.currentThread().getId();//获取该锁的过期时间,如果该锁没被持有,会返回一个null,如果被持有 会返回一个过期时间Long ttl = this.tryAcquire(leaseTime, unit, threadId);if (ttl != null) {//ttl不为null,说明锁已经被抢占了RFuture<RedissonLockEntry> future = this.subscribe(threadId);this.commandExecutor.syncSubscription(future);try {//开始循环获取锁while(true) {//刚进如循环先尝试获取锁,获取成功返回null,跳出循环,获取失败,则继续往下走ttl = this.tryAcquire(leaseTime, unit, threadId);if (ttl == null) {return;}if (ttl >= 0L) {//如果过期时间大于0,则调用getLatch// 返回一个信号量,开始进入阻塞,阻塞时长为上一次锁的剩余过期时长,并且让出cup//有阻塞必然有唤醒,位于解锁操作中this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} else {this.getEntry(threadId).getLatch().acquire();}}} finally {this.unsubscribe(future, threadId);}}}

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, final long threadId) {//如果leaseTime != -1,即不等于默认值,则表示手动设置了过期时间if (leaseTime != -1L) {return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);} else {//如果leaseTime = -1,表示使用默认方式,即使用看门狗默认实现自动续期RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);ttlRemainingFuture.addListener(new FutureListener<Long>() {public void operationComplete(Future<Long> future) throws Exception {//如果tryLockInnerAsync执行成功if (future.isSuccess()) {//获取过期时间Long ttlRemaining = (Long)future.getNow();//过期时间为空,表示加锁成功if (ttlRemaining == null) {//开启刷新重置过期时间步骤RedissonLock.this.scheduleExpirationRenewal(threadId);}}}});return ttlRemainingFuture;}}

//  lua脚本尝试抢占锁,失败返回锁过期时间<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {this.internalLockLeaseTime = unit.toMillis(leaseTime);//直接使用lua脚本发起命令//通过lua脚本可以看出,redisson加锁除了使用自定义的名字以外,还要使用uuid// 加上当前线程的threadId组合,以自定义名字作hash的key,使用return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command,//如果该锁未被占有,则设置锁,设置过期时间,过期时间为 internalLockLeaseTime ,然后返回null"if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]);return nil; end; " +//如果锁已经被占有,判断是否是重入锁,如果是重入锁,则将value增加1 ,代表重入,并且设置过期时间,返回null。"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; " +//如果已经被站有所,且不是重入锁,则返回过期时间"return redis.call('pttl', KEYS[1]);",Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});}

看门狗续命

//看门狗续命机制private void scheduleExpirationRenewal(final long threadId) {//首先会判断该线程是否已经再重置时间的map中,仅仅第一次进来是空的。if (!expirationRenewalMap.containsKey(this.getEntryName())) {//使用了看门狗默认的时间(30秒) 除以3 ,也就是延迟10秒后执行Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {public void run(Timeout timeout) throws Exception {//判断是否该线程是否还持有锁,如果持有,返回1,并且设置过期时间,如果没持有,返回0RFuture<Boolean> future = RedissonLock.this.commandExecutor.evalWriteAsync(RedissonLock.this.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(RedissonLock.this.getName()), new Object[]{RedissonLock.this.internalLockLeaseTime, RedissonLock.this.getLockName(threadId)});future.addListener(new FutureListener<Boolean>() {public void operationComplete(Future<Boolean> future) throws Exception {//从map中移除该线程,这样下次再调用该方法仍然可以执行RedissonLock.expirationRenewalMap.remove(RedissonLock.this.getEntryName());if (!future.isSuccess()) {RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", future.cause());} else {if ((Boolean)future.getNow()) {//当lua脚本返回1表是true,也就是仍然持有锁,则递归调用该方法,RedissonLock.this.scheduleExpirationRenewal(threadId);}}}});}}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);if (expirationRenewalMap.putIfAbsent(this.getEntryName(), task) != null) {task.cancel();}}}

2、unlock

源码

    public RFuture<Void> unlockAsync(final long threadId) {final RPromise<Void> result = new RedissonPromise();//调用lua脚本释放锁RFuture<Boolean> future = this.unlockInnerAsync(threadId);future.addListener(new FutureListener<Boolean>() {public void operationComplete(Future<Boolean> future) throws Exception {if (!future.isSuccess()) {result.tryFailure(future.cause());} else {Boolean opStatus = (Boolean)future.getNow();//如果锁状态为null,表示存在异常,为正常释放锁之前,被别人占领锁了if (opStatus == null) {IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + RedissonLock.this.id + " thread-id: " + threadId);result.tryFailure(cause);} else {//如果返回0.为false 表示可重入锁,不取消重置过期时间,//返回1 为true,表示已解锁,取消重置过期时间if (opStatus) {RedissonLock.this.cancelExpirationRenewal();}//解锁result.trySuccess((Object)null);}}}});return result;}

protected RFuture<Boolean> unlockInnerAsync(long threadId) {return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,//当key不存在,表示锁未被持有,说明不用解锁了,返回1 ,1在后续表示取消重置过期时间"if (redis.call('exists', KEYS[1]) == 0) then redis.call('publish', KEYS[2], ARGV[1]); return 1; end;" +//key存在,但是持有锁的线程不是当前线程,返回null,后面会提出一个异常"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil;end; " +//锁状态-1后仍然大于0,表示可重入锁,仍处于锁定状态,返回0,0在后续表示 不做处理,仍然重置过期时间"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); return 0; " +//返回锁状态不大于0,正常解锁,返回1,1在后续表示取消重置过期时间"else redis.call('del', KEYS[1]); redis.call('publish', KEYS[2], ARGV[1]); return 1; end; " +"return nil;", Arrays.asList(this.getName(), this.getChannelName()), new Object[]{LockPubSub.unlockMessage, this.internalLockLeaseTime, this.getLockName(threadId)});}

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

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

相关文章

JMeter压测常见面试问题

1、JMeter可以模拟哪些类型的负载&#xff1f; JMeter可以模拟各种类型的负载&#xff0c;包括但不限于Web应用程序、API、数据库、FTP、SMTP、JMS、SOAP / RESTful Web服务等。这使得JMeter成为一个功能强大且灵活的压力测试工具。 2、如何配置JMeter来进行分布式压力测试&a…

防爆智能安全帽、防爆手持终端,防爆智能矿灯守护安全,在煤矿安全生产远程可视化监管中的应用

煤矿安全新守护&#xff1a;如何通过防爆智能装备实现远程可视化监管 煤矿是国民经济的重要支柱产业&#xff0c;但长期以来&#xff0c;安全生产事故的频发一直是困扰煤矿行业发展的严峻问题。安全生产事故不仅危及矿工的生命安全&#xff0c;也对企业和地方经济造成了重大的…

驯服大数据的超强利器——PySpark数据处理引擎

你是否曾经为了处理大规模数据而烦恼&#xff1f;是否曾经为了解决日常的数据科学挑战而彻夜难眠&#xff1f;现在&#xff0c;Spark数据处理引擎正在向你敞开大门。这是一个惊人的分析工厂&#xff0c;输入原始数据&#xff0c;输出洞察。 PySpark&#xff0c;作为Spark的核心…

壹基金宣传进瑞金河背街社区 安全家园项目防灾减灾深入人心

11月16日下午&#xff0c;瑞金赋能公益、蓝天救援队等联合象湖镇河背街社区开展家庭安全计划社区活动包挑战赛活动暨壹基金安全家园项目防灾减灾宣传社区行活动。活动得到了救助儿童会北京代表处、壹基金、艾特公益、益心益意公益的指导&#xff0c;得到了阿里巴巴公益平台广大…

服务器 jupyter 文件名乱码问题

对于本台电脑&#xff0c;autodl服务器&#xff0c;上传中文文件时&#xff0c;从压缩包名到压缩包里的文件名先后会出现中文乱码的问题。 Xftp 首先是通过Xftp传输压缩包到Autodl服务器&#xff1a; 1、打开Xftp&#xff0c;进入软件主界面&#xff0c;点击右上角【文件】菜…

C++设计模式之工厂模式(上)——简单工厂模式

工厂模式 概述简单工厂模式介绍示例示例使用运行结果缺点 概述 工厂模式属于一种创建型设计模式。其可以分为简单工厂模式&#xff0c;工厂模式和抽象工厂模式。工厂模式分为上、中、下三篇&#xff0c;本篇主要介绍简单工厂模式。 简单工厂模式 介绍 简单工厂模式可以理解…

竞赛选题 题目: 基于深度学习的疲劳驾驶检测 深度学习

文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 &#x1f525; 优…

二百零七、Flume——Flume实时采集5分钟频率的Kafka数据直接写入ODS层表的HDFS文件路径下

一、目的 在离线数仓中&#xff0c;需要用Flume去采集Kafka中的数据&#xff0c;然后写入HDFS中。 由于每种数据类型的频率、数据大小、数据规模不同&#xff0c;因此每种数据的采集需要不同的Flume配置文件。玩了几天Flume&#xff0c;感觉Flume的使用难点就是配置文件 二、…

【电路笔记】-星三角变换(Star-Delta Transformation)

星三角变换&#xff08;Star-Delta Transformation&#xff09; 文章目录 星三角变换&#xff08;Star-Delta Transformation&#xff09;1、概述1.1 单相配置1.2 多相配置 2、三相连接2.1 Y配置2.2 Δ配置 3、Y-Δ 和 Δ-Y 变换3.1 Y-Δ变换3.2 Δ-Y变换3.3 应用 4、总结 本文…

2023年DevOps国际峰会暨BizDevOps企业峰会(DOIS北京站)-核心PPT资料下载

一、峰会简介 在数字化转型的大背景下&#xff0c;企业选择实践 DevOps 来提升 IT 效能成为常态&#xff0c;BizDevOps 作为企业自身数字化变革的重要主题之一&#xff0c;需要全行业共同努力促进繁荣和发展。从 DevOps 到 BizDevOps&#xff0c;业务与技术如何融合&#xff1…

大模型的交互能力

摘要&#xff1a; 基础大模型显示出明显的潜力&#xff0c;可以改变AI系统的开发人员和用户体验&#xff1a;基础模型降低了原型设计和构建AI应用程序的难度阈值&#xff0c;因为它们在适应方面的样本效率&#xff0c;并提高了新用户交互的上限&#xff0c;因为它们的多模式和生…

Flink 替换 Logstash 解决日志收集丢失问题

在某客户日志数据迁移到火山引擎使用 ELK 生态的案例中&#xff0c;由于客户反馈之前 Logstash 经常发生数据丢失和收集性能较差的使用痛点&#xff0c;我们尝试使用 Flink 替代了传统的 Logstash 来作为日志数据解析、转换以及写入 ElasticSearch 的组件&#xff0c;得到了该客…

为何越来越多的程序员纷纷转行网络安全?

目前&#xff0c;我国IT行业的人才结构不断升级&#xff0c;公司对程序员的要求越来越高&#xff0c;出现了大量的裁员现象&#xff0c;导致很多的程序员纷纷想转行的想法。 可能对于早期的程序员而言&#xff0c;学好编程语言就能找到比较好的工作。而现在伴随着互联网的不断发…

十一 动手学深度学习v2计算机视觉 ——微调

一、网络架构 一个神经网络一般可以分成两块 特征抽取&#xff0c;将原始像素变成容易线性分割的特征。线性分类器来做分类。 二、训练 是一个目标数据集上的正常训练任务&#xff0c; 但使用更强的正则化 使用更小的学习率使用更少的数据迭代 源数据集远远复杂于目标数据集…

如何用Python爬取全国高校数据?

前言 Python是一门强大的编程语言&#xff0c;它可以用于爬取互联网上的各种数据。在这篇文章中&#xff0c;我们将学习如何使用Python爬取全国高校数据&#xff0c;并使用代理IP进行爬取。 本文主要分为以下几个部分&#xff1a; 数据来源及需求安装依赖包及导入模块爬取全…

Python 提高篇学习笔记(一):深拷贝和浅拷贝

文章目录 一、什么是对象的引用二、深拷贝和浅拷贝2.1 浅拷贝(Shallow Copy)2.2 深拷贝(Deep Copy)2.3 copy.copy和copy.deepcopy的区别 一、什么是对象的引用 在 Python 中&#xff0c;对象的引用是指变量指向内存中某个对象的地址或标识符。当你创建一个新的对象(比如一个整…

k8s无法删除pv,pvc问题

问题&#xff1a; 在k8s里面创建了pv&#xff0c;pvc删除时报错&#xff1a;error: resource(s) were provided, but no name was specified 解决&#xff1a; 正确的删除顺序&#xff1a;1.先删除pod2.再删除pv 3.在删除pvc 删除pv&#xff0c;pvc命令&#xff1a; kubect…

“下一代云”白皮书发布:PaaS成为核心增长动力,腾讯云市场份额第二

“市场需求进一步向PaaS和SaaS层进发&#xff0c;使之成为公有云服务市场增长的主要动力。”11月22日&#xff0c;全球领先的IT研究和咨询公司国际数据公司&#xff08;IDC&#xff09;联合腾讯云发布“下一代云”白皮书——《聚焦平台能力&#xff0c;支撑智能化业务发展》指出…

电大搜题——让学习变得轻松高效

作为一名现代学者&#xff0c;您一定时刻关注着教育领域的进展和创新。今天&#xff0c;我将向大家介绍一个名为“电大搜题”的神奇工具&#xff0c;它将为您的学习之路带来一场完美的革命。 在快节奏的现代社会中&#xff0c;学习已经成为每个人追求成功的必经之路。然而&…

微服务实战系列之加密RSA

前言 在这个时代&#xff0c;我们选择的人生目标已丰富多彩&#xff0c;秉持的人生态度也千差万别&#xff1a; 除了吃喝玩乐&#xff0c;还有科技探索&#xff1b; 除了CityWalk&#xff0c;还有“BookWalk”&#xff1b; 除了走遍中国&#xff0c;还有走遍世界&#xff1b; …