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: xxxxxxx
  password: xxxxxx
  max-active: 8
  max-idle: 500
  max-wait: 1
  min-idle: 0
  port: 6379
  timeout: 1000ms
  database: 0

redisson配置类

@Configuration
public class RedisConfig {
   //最简单的redisson初始化配置
    @Bean
    public 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 {
    @Autowired
    private 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中的future
            ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,
                    TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        }
 
        //这里也是类似netty Future 的addListener,在future内容执行完成后执行
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            if (e != null) {
                return;
            }
 
            // lock acquired
            if (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() {
            @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 " + getRawName() + " expiration", e);
                        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
                        return;
                    }
 
                    if (res) {
                        //如果 没有报错,就再次定时延期
                        // reschedule itself
                        renewExpiration();
                    } 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脚本来进行延时

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

贪心算法篇2

“星辰野草&#xff0c;造出无边的天地~” 最⻓递增⼦序列 (1) 题目解析 (2) 算法原理 class Solution { public:int lengthOfLIS(vector<int>& nums) {// 使用dp int n nums.size(), ret 1;// 初始化为1vector<int> dp(n1,1);// 从第二个位置…

彻底学会系列:一、机器学习之线性回归

1.基本概念 线性回归&#xff1a; 有监督学习的一种算法。主要关注多个因变量和一个目标变量之间的关系。 因变量&#xff1a; 影响目标变量的因素&#xff1a; X 1 , X 2 . . . X_1, X_2... X1​,X2​... &#xff0c;连续值或离散值。 目标变量&#xff1a; 需要预测的值: t…

黑豹程序员-ElementPlus选择图标器

ElementPlus组件提供了很多图标svg 如何在你的系统中&#xff0c;用户可以使用呢&#xff1f; 这就是图标器&#xff0c;去调用ElementPlus的icon组件库&#xff0c;展示到页面&#xff0c;用户选择&#xff0c;返回选择的组件名称。 效果 代码 <template><el-inpu…

双非本科准备秋招(16.1)—— 力扣二叉树

1、101. 对称二叉树 检查是否对称&#xff0c;其实就是检查左节点等不等于右节点&#xff0c;我们可以用递归来做。 如果左右节点都为null&#xff0c;说明肯定对称呀&#xff0c;返回true。 如果一个为null一个不为null&#xff0c;或者左右的值不相等&#xff0c;则为false。…

安卓文件传输 -- Android File Transfer

Android File Transfer是一款专门为Mac用户设计的软件&#xff0c;用于在Android设备与Mac之间传输文件。该软件支持多种文件类型&#xff0c;包括图片、音乐、视频、文档等&#xff0c;使用户能够轻松地将文件从Android设备传输到Mac或从Mac传输到Android设备。 Android File…

MoneyBox: 1靶机

主机发现 端口扫描 爆破到了ftp的账号密码&#xff0c;这是ftp的允许匿名登陆&#xff0c;密码为空也是可以的。 ftp 192.168.10.131 输入账号anonymous&#xff0c;密码为空或者anonymous1234也可以&#xff0c;连接上ftp后ls一下&#xff0c;看看有什么内容。 目录爆破发现bl…

NLP入门系列—分词 Tokenization

NLP入门系列—分词 Tokenization 分词是 NLP 的基础任务&#xff0c;将句子&#xff0c;段落分解为字词单位&#xff0c;方便后续的处理的分析。 本文将介绍分词的原因&#xff0c;中英文分词的3个区别&#xff0c;中文分词的3大难点&#xff0c;分词的3种典型方法。最后将介…

[office] Excel2007在工作簿中创建区域名称 #职场发展#经验分享

Excel2007在工作簿中创建区域名称 Excel 提供了几种不同的方法来创建区域名称。但在开始之前&#xff0c;必须注意关于可接受内容的重要规则: 名称不能含有空格。可以用一个下划线字符来代替空格(如Annual Total ) 。 可以使用字母和数字的任意组合&#xff0c;但是名称必须以…

备份RK35XX 设备的ubuntu根文件系统的方法

简介 我们使用 RK35XX 提供的SDK包制作了一个完整的 ubuntu 镜像,烧录到设备中,会在设备中安装很多我们需要的软件,运行的一些自己写的脚本和业务程序,当我们有很多台设备时,不可能每台都一个个去安装,此时我们就需要一个工具来备份当前设备的根文件系统,然后再放到 SD…

ywtool dhcp命令

一.dhcp功能介绍 就是通过脚本实现dhcp地址池的增、删、改、查这几个功能日志文件路径: /var/log/ywtools/ywtool-dhcp.log/usr/local/ywtools/config/config.ini中account参数(ywtool dhcp这个命令用的,但是这个命令只能配置1个地址池,所以这里面的参数没什么意义) 二.配置…

生物素 PEG4 甲基四嗪,Biotin-PEG4-methyltetrazine,用于标记、追踪和分离特定的分子或细胞

生物素四聚乙二醇甲基四嗪&#xff0c;生物素 PEG4 甲基四嗪&#xff0c;Biotin-PEG4-methyltetrazine&#xff0c;用于标记、追踪和分离特定的分子或细胞 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;生物素四聚乙二醇甲基四嗪&#xff0c;生物素 PEG4 甲基四嗪…

RK Camera hal 图像处理

soc&#xff1a;RK3568 system:Android12 今天发现外接的USBCamera用Camera 2API打开显示颠倒&#xff0c;如果在APP 里使用Camera1处理这块接口较少&#xff0c;调整起来比较麻烦 RK Camera hal位置&#xff1a;hardware/interfaces/camera 核心的文件在&#xff1a; 开机…

windows 搭建nginx http服务

下载 下面链接直接点击下载&#xff0c;下载的就是包含rtmp服务器相关功能的&#xff0c;只不过需要配置下 Index of /download/ (ecsds.eu) nginx 1.7.11.3 Gryphon.zip直接点击额下面的连接即可下载 http://nginx-win.ecsds.eu/download/nginx%201.7.11.3%20Gryphon.zip …

Go语言Gin框架安全加固:全面解析SQL注入、XSS与CSRF的解决方案

前言 在使用 Gin 框架处理前端请求数据时&#xff0c;必须关注安全性问题&#xff0c;以防范常见的攻击。本文将探讨 Gin 框架中常见的安全问题&#xff0c;并提供相应的处理方法&#xff0c;以确保应用程序的稳健性和安全性。 处理前端请求数据时&#xff0c;确保应用程序的…

GaussDB新体验,新零售选品升级注入新思路【华为云GaussDB:与数据库同行的日子】

选品思维&#xff1a;低频VS高频 一个的商超&#xff0c;假设有50个左右的品类&#xff0c;每个品类下有2到10个不等的商品。然而如此庞大的商品&#xff0c;并非所有都是高频消费品。 结合自身日常的消费习惯&#xff0c;对于高频和低频的区分并不难。一般大型家电、高端礼盒…

802.11n 802.11ac (WiFi 4/5 )的核心要点

802.11n 802.11ac &#xff08;WiFi 4/5 &#xff09;是什么&#xff1f; WiFi 4&#xff1a; Ieee 802.11n Enhancements for High Throughput &#xff08;HT&#xff09; WiFi 5&#xff1a; Ieee 802.11ac Enhancements for Very High Throughput &#xff08;VHT&#x…

前缀和 acwing

思路&#xff1a;两个数组&#xff0c;一个数组用来保存数据&#xff0c;一个数组来求对应项的和 前缀和S[r]-s[r-1] 空出来下标0 从1开始 方便表示&#xff0c;防止越界 c代码实现: #include<iostream> using namespace std; const int N1000000; int a[N],s[N]; …

Linux底层基础知识

一.汇编&#xff0c;C语言&#xff0c;C&#xff0c;JAVA之间的关系 汇编&#xff0c;C语言&#xff0c;C可以通过不同的编译器&#xff0c;编译成机器码。而java只能由Java虚拟机识别。Java虚拟机可以看成一个操作系统&#xff0c;Java虚拟机是由汇编&#xff0c;C&#xff0c…

【刷题题解】最长回文子序列

给你一个字符串 s &#xff0c;找出其中最长的回文子序列&#xff0c;并返回该序列的长度。 子序列定义为&#xff1a;不改变剩余字符顺序的情况下&#xff0c;删除某些字符或者不删除任何字符形成的一个序列 这道题&#xff0c;一眼动态规划&#xff0c;但是即使动起来也规划…

总结了一下中继引擎(can中继器,TCP总机器)开发实际经验

多路数据进行中继的研究 1.数据中继的概念 数据中继是一种数据传输技术&#xff0c;用于在两个通信设备之间提供数字信号的传输。它利用数字信道传输数据信号&#xff0c;可以提供永久性和半永久性连接的数字数据传输信道。 数据中继的主要作用是提高通信质量和可靠性&#xf…