redisson watchdog 原理

news2024/11/23 14:42:54

目录

  • 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 {
    @Autowired
    private 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

   @Override
    public 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() {
            @Override
            public 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 itself
                        renewExpiration();
                    }
                });
            }
        }, 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对象,延迟任务不再调用进而不再自动续期,锁被释放。

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

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

相关文章

黑悟空!一区预定!原创首发!SLWCHOA-Transformer-LSTM混合改进策略的黑猩猩优化算法多变量时间序列预测

黑悟空&#xff01;一区预定&#xff01;原创首发&#xff01;SLWCHOA-Transformer-LSTM混合改进策略的黑猩猩优化算法多变量时间序列预测 目录 黑悟空&#xff01;一区预定&#xff01;原创首发&#xff01;SLWCHOA-Transformer-LSTM混合改进策略的黑猩猩优化算法多变量时间序…

帮助检测SQL注入漏洞的工具

目录 SQLMap与Burp Suite相比&#xff0c;哪个更适合进行大规模的SQL注入检测&#xff1f; OWASP ZAP在检测SQL注入时的优势体现在哪些方面&#xff1f; 对于SQL注入漏洞检测&#xff0c;Havij和acunetix有什么区别&#xff1f; 在检测SQL注入漏洞方面&#xff0c;有几款工具…

shell脚本-采集容器内自定义端口tcp连接数并通过http接口推送到Prometheus

目录 1、脚本编写 2、脚本说明 3、运行脚本 1、脚本编写 脚本监控服务器 5000 端口的 TCP 连接数。使用 netstat 工具获取连接数&#xff0c;并通过一个简单的 shell 服务器提供 /connect 接口。具体功能如下&#xff1a; vim prometheus_tcp_monitor.sh 编写脚本&#…

Docker 安装消息队列RabbitMQ

拉取镜像 docker pull rabbitmq拉取最新镜像 创建并运行 docker run -d --hostname my-rabbit --name rabbit-p 15672:15672 -p 5673:5672rabbitmq开启Web管理 进入容器 docker exec -it rabbitmq /bin/bash开启web管理 rabbitmq-plugins enable rabbitmq_managementhttp:…

一文通透DeepSeek-V2(改造Transformer的中文模型):从DeepSeek LLM到DeepSeek-V2的MLA与MoE

前言 成就本文有以下三个因素 24年5.17日&#xff0c;我在我司一课程「大模型与多模态论文100篇」里问道&#xff1a;大家希望我们还讲哪些论文 一学员朋友小栗说&#xff1a;幻方发布的deepseek-v224年5.24日&#xff0c;我司一课程「大模型项目开发线上营1」里的一学员朋友…

Typora + PicGo + SMMS 实现markdown格式文档图片上传

Typora PicGo SMMS 实现图片自动上传 1. Typora 软件安装2. PicGo 的安装2.1 下载 PicGo 3. 配置 SMMS 图床服务3.1 注册并登录 SMMS3.2 获取 API Token 4. 软件配置4.1 Typora 图床设置4.2 PicGo 配置 5. 使用 Typora 实现图片自动上传 1. Typora 软件安装 Typora中文版是一…

排序1

一、概述 直接插入排序 是稳定排序 二、插入排序 1&#xff09;直接插入排序 2&#xff09;折半插入排序 3)希尔排序 、 三、交换排序 1&#xff09;冒泡排序 2&#xff09;快速排序

three.js 着色器学习 聚集地

预览地址&#xff1a;https://z2586300277.github.io/three-cesium-examples 国内站点预览&#xff1a;http://threehub.cn github: https://github.com/z2586300277/three-cesium-examples

swift微调Qwen-7B大模型

环境说明&#xff1a; CUDA相关环境已搭建完成&#xff0c;不会装CUDA环境可参照我的其它文章&#xff1b; 显卡&#xff1a;4张3090 1、安装swift环境 #从源码安装 git clone https://github.com/modelscope/swift.git cd swift pip install -e .[llm] pip install -e .[e…

文件读写与缓存机制

文件读写与缓存机制 写文件&#xff1a; 1&#xff1a;stdio函数库 fopen/fwrite/fflush/fclose File* fp2: POSIX系统级别函数 open/write/close fd3&#xff1a;Windows系统级别函数 CreateFile/WriteFile/CloseHandle**同步数据到磁盘&#xff1a;**FlushFileBuffers、f…

永磁同步电机高性能控制算法(13)后续篇—— 基于高阶扩张状态观测器(ESO)的无模型预测控制(MFPC)

1.前言 前文已经介绍过了高阶ESO相对于传统ESO的优势。 https://zhuanlan.zhihu.com/p/703039702https://zhuanlan.zhihu.com/p/703039702 但是当时搭的ESO有点问题。把公式修正之后&#xff0c;发现前文用的改进四阶ESO无法使用。 今天来解释一下为什么改进4阶ESO无法使用…

SystemTap(stap)架构和原理介绍,以及脚本编写举例

1 SystemTap简介 SystemTap是一个诊断Linux系统性能或功能问题的开源工具。它允许开发人员和系统管理员深入研究内核甚至用户空间应用程序的行为&#xff0c;以便发现错误状态、性能问题&#xff0c;或者仅仅为了解系统是如何工作的。它使得对运行时的Linux系统进行诊断调式变…

递归算法及应用

一.简介 1.介绍 递归&#xff08;Recursion&#xff09;在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法&#xff0c;其核心思想是分治策略。 在日常开发中&#xff0c;我们使用循环语句远远大于递归&#xff0c;但这不能说明递归就没有用武之地&am…

服务器(百度云)部署项目(jar包)

java项目打包成jar包&#xff1a;clean------compile------install jar包上传到服务器上 和jar包相同的文件里&#xff0c;创建Dockerfile文件。 Dockerfile文件的内容informationerasure是jar包名&#xff0c;这里可根据自己定义的名字进行更换。 Dockerfile文件内容&#x…

chrome打印dom节点不显示节点信息

正常直接console dom节点 代码改成 var parser new DOMParser(); var docDom parser.parseFromString(testHtml, text/html); console.log(docDom) let htmlHeader ref< HTMLElement | null>(null) let htmlBoby ref< HTMLElement | null>(null) htmlHeader.v…

Datawhale AI 夏令营 第五期 CV Task1

活动简介 活动链接&#xff1a;Datawhale AI 夏令营&#xff08;第五期&#xff09; 以及CV里面的本次任务说明&#xff1a;Task 1 从零上手CV竞赛 链接里的教程非常详细&#xff0c;很适合小白上手&#xff0c;从报名赛事到使用服务器平台再到跑模型&#xff0c;手把手教&…

【Go语言基础】调度器模型GPM与垃圾回收器GC

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了Go语言学习的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于Go语言趣学指南进行的&#xff0c;每个知识点的修正和深入主要参…

ubuntu系统在线安装下载firefox-esr流览器

1、在线firefox流览器 Firefox ESR(Extended Support Release)是火狐浏览器的长期支持版本&#xff0c;针对同一个主版本提供一年左右的安全性与稳定性支持。如果您因为火狐浏览器改版而导致有原本能用的功能变得不能使用的话(例如Firefox 64.0把RSS订阅的功能拿掉了)&#xf…

IO进程day02(文件IO)

目录 【1】什么是文件IO 1》概念 2》特点 3》操作 【2】函数接口 1》打开文件open() 思考&#xff1a;文件IO和标准IO的打开方式的对应关系 2》关闭文件 close() 3》读写文件 read write 1> 读文件 read() 2> 写文件 write() 练习&#xff1a;文件IO实现cp…

使用AWS的EC2服务如何降低成本

在现代企业中&#xff0c;云计算已经成为推动业务创新和发展的重要工具。亚马逊云服务&#xff08;AWS&#xff09;的弹性计算云&#xff08;EC2&#xff09;提供了灵活的计算能力&#xff0c;企业可以根据需求快速部署和管理应用。然而&#xff0c;如何在使用EC2服务的过程中有…