Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一文详解分布式锁的看门狗机制

一文详解分布式锁的看门狗机制

作者头像
Java极客技术
发布于 2023-11-29 04:54:13
发布于 2023-11-29 04:54:13
1.6K00
代码可运行
举报
文章被收录于专栏:Java极客技术Java极客技术
运行总次数:0
代码可运行

每天早上七点三十,准时推送干货

我们今天来看看这个 Redis 的看门狗机制,毕竟现在还是有很多是会使用 Redis 来实现分布式锁的,我们现在看看这个 Redis 是怎么实现分布式锁的,然后我们再来分析这个 Redis 的看门狗机制,如果没有这个机制,很多使用 Redis 来做分布式锁的小伙伴们,经常给导致死锁。

Redis 实现分布式锁

Redis实现分布式锁,最主要的就是这几个条件

获取锁

  • 互斥:确保只能有一个线程获取锁
  • 非阻塞:尝试一次,成功返回true,失败返回false

释放锁

  • 手动释放
  • 超时释放:获取锁时添加一个超时时间

上代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

    @Resource
    private RedisTemplate redisTemplate;

    public static final String UNLOCK_LUA;

    /**
     * 释放锁脚本,原子操作
     */
    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }


    /**
     * 获取分布式锁,原子操作
     * @param lockKey
     * @param requestId 唯一ID, 可以使用UUID.randomUUID().toString();
     * @param expire
     * @param timeUnit
     * @return
     */
    public boolean tryLock(String lockKey, String requestId, long expire, TimeUnit timeUnit) {
        try{
            RedisCallback<Boolean> callback = (connection) -> {
                return connection.set(lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")), Expiration.seconds(timeUnit.toSeconds(expire)), RedisStringCommands.SetOption.SET_IF_ABSENT);
            };
            return (Boolean)redisTemplate.execute(callback);
        } catch (Exception e) {
            log.error("redis lock error.", e);
        }
        return false;
    }

    /**
     * 释放锁
     * @param lockKey
     * @param requestId 唯一ID
     * @return
     */
    public boolean releaseLock(String lockKey, String requestId) {
        RedisCallback<Boolean> callback = (connection) -> {
            return connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN ,1, lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")));
        };
        return (Boolean)redisTemplate.execute(callback);
    }

    /**
     * 获取Redis锁的value值
     * @param lockKey
     * @return
     */
    public String get(String lockKey) {
        try {
            RedisCallback<String> callback = (connection) -> {
                return new String(connection.get(lockKey.getBytes()), Charset.forName("UTF-8"));
            };
            return (String)redisTemplate.execute(callback);
        } catch (Exception e) {
            log.error("get redis occurred an exception", e);
        }
        return null;
    }

这种实现方式就是相当于我们直接使用 Redis 来自己实现的分布式锁,但是也不是没有框架给我们来实现,那就是Redission。而看门狗机制是Redission提供的一种自动延期机制,这个机制使得Redission提供的分布式锁是可以自动续期的。

为什么需要看门狗机制

分布式锁是不能设置永不过期的,这是为了避免在分布式的情况下,一个节点获取锁之后宕机从而出现死锁的情况,所以需要个分布式锁设置一个过期时间。但是这样会导致一个线程拿到锁后,在锁的过期时间到达的时候程序还没运行完,导致锁超时释放了,那么其他线程就能获取锁进来,从而出现问题。

所以,看门狗机制的自动续期,就很好地解决了这一个问题。

Redisson已经帮我们实现了这个分布式锁,我们需要的就是调用,那么我们来看看 Redisson 的源码,他是如何来实现看门狗机制的。

tryLock

RedissonLock类下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
    return tryLock(waitTime, -1, unit);
}

  • waitTime:获取锁的最大等待时间(没有传默认为-1)
  • leaseTime:锁自动释放的时间(没有传的话默认-1)
  • unit:时间的单位(等待时间和锁自动释放的时间单位)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @Override
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        long threadId = Thread.currentThread().getId();
        Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) {
            return true;
        }
        
        time -= System.currentTimeMillis() - current;
        if (time <= 0) {
            acquireFailed(waitTime, unit, threadId);
            return false;
        }
        
        current = System.currentTimeMillis();
        RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
        if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
            if (!subscribeFuture.cancel(false)) {
                subscribeFuture.onComplete((res, e) -> {
                    if (e == null) {
                        unsubscribe(subscribeFuture, threadId);
                    }
                });
            }
            acquireFailed(waitTime, unit, threadId);
            return false;
        }

        try {
            time -= System.currentTimeMillis() - current;
            if (time <= 0) {
                acquireFailed(waitTime, unit, threadId);
                return false;
            }
        
            while (true) {
                long currentTime = System.currentTimeMillis();
                ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    return true;
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(waitTime, unit, threadId);
                    return false;
                }

                // waiting for message
                currentTime = System.currentTimeMillis();
                if (ttl >= 0 && ttl < time) {
                    subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                } else {
                    subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
                }

                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(waitTime, unit, threadId);
                    return false;
                }
            }
        } finally {
            unsubscribe(subscribeFuture, threadId);
        }
//        return get(tryLockAsync(waitTime, leaseTime, unit));
    }

上面这一段代码最主要的内容讲述看门狗机制的实际上应该算是 tryAcquire

最终落地为tryAcquireAsync

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//如果获取锁失败,返回的结果是这个key的剩余有效期
        RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(waitTime, this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        //上面获取锁回调成功之后,执行这代码块的内容
        ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
            //不存在异常
            if (e == null) {
                //剩余有效期为null
                if (ttlRemaining == null) {
                    //这个函数是解决最长等待有效期的问题
                    this.scheduleExpirationRenewal(threadId);
                }

            }
        });
        return ttlRemainingFuture;

调用tryLockInnerAsync,如果获取锁失败,返回的结果是这个key的剩余有效期,如果获取锁成功,则返回null。

获取锁成功后,如果检测不存在异常并且获取锁成功(ttlRemaining == null)

那么则执行this.scheduleExpirationRenewal(threadId);来启动看门狗机制。

看门狗机制提供的默认超时时间是30*1000毫秒,也就是30秒

如果一个线程获取锁后,运行程序到释放锁所花费的时间大于锁自动释放时间(也就是看门狗机制提供的超时时间30s),那么Redission会自动给redis中的目标锁延长超时时间。

在Redission中想要启动看门狗机制,那么我们就不用获取锁的时候自己定义leaseTime(锁自动释放时间)。

但是 Redisson 和我们自己定义实现分布式锁不一样,如果自己定义了锁自动释放时间的话,无论是通过lock还是tryLock方法,都无法启用看门狗机制。

所以你了解分布式锁的看门狗机制了么?


喜欢就分享

认同就点赞

支持就在看

一键四连,你的offer也四连

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-11-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java极客技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
分布式锁实现大型连续剧之(一):Redis
单机环境下我们可以通过JAVA的Synchronized和Lock来实现进程内部的锁,但是随着分布式应用和集群环境的出现,系统资源的竞争从单进程多线程的竞争变成了多进程的竞争,这时候就需要分布式锁来保证。
java架构师
2018/09/26
1.2K0
redission 锁机制
前段时间,有小伙伴问我,redission锁的原理,看门狗的作用,和一些实际开发中的场景,当时并没有给他比较完整的解答,后来我查了资料对redission做了一个总结,在这里分享给小伙伴们
六个核弹
2023/04/26
6210
redisson分布式锁实现原理
Redisson是一个使用Java编写的开源库,它提供了对Redis数据库的访问和操作的封装,并在此基础上提供了各种分布式功能,包括分布式锁。
叔牙
2023/09/07
1.3K0
redisson分布式锁实现原理
分布式锁-这一篇全了解(Redis实现分布式锁完美方案)[通俗易懂]
在某些场景中,多个进程必须以互斥的方式独占共享资源,这时用分布式锁是最直接有效的。
全栈程序员站长
2022/07/28
1.4K0
分布式锁-这一篇全了解(Redis实现分布式锁完美方案)[通俗易懂]
字节二面:Spring Boot Redis 可重入分布式锁实现原理?
当一个线程执行一段代码成功获取锁之后,继续执行时,又遇到加锁的代码,可重入性就就保证线程能继续执行,而不可重入就是需要等待锁释放之后,再次获取锁成功,才能继续往下执行。
码哥字节
2024/01/30
4510
字节二面:Spring Boot Redis 可重入分布式锁实现原理?
【📕分布式锁通关指南 06】源码剖析redisson可重入锁之加锁
它的核心逻辑也很简单:首先检查锁是否存在,如果不存在,则直接加锁,且设置重入次数为1;如果存在,先检查是否是当前线程的锁,如果是,则重入次数+1,如果不是,则返回锁的剩余过期时间。
别惹CC
2025/03/04
1010
【📕分布式锁通关指南 06】源码剖析redisson可重入锁之加锁
又长又细,万字长文带你解读Redisson分布式锁的源码
上一篇文章写了Redis分布式锁的原理和缺陷,觉得有些不过瘾,只是简单的介绍了下Redisson这个框架,具体的原理什么的还没说过呢。趁过年放几天假,反正闲着也是闲着,不如把Redisson的源码也学习一遍好了。
鄙人薛某
2021/02/26
7110
又长又细,万字长文带你解读Redisson分布式锁的源码
分布式锁—2.Redisson的可重入锁二
如果某个客户端上锁后,过了几分钟都没释放掉这个锁,而锁对应的key一开始的过期时间其实只设置了30秒而已,那么在这种场景下这个锁就不能在上锁的30秒后被自动释放。
东阳马生架构
2025/05/09
1180
【📕分布式锁通关指南 08】源码剖析redisson可重入锁之释放及阻塞与非阻塞获取
lock:默认不响应中断,但可以通过lockInterruptibly方法支持中断
别惹CC
2025/03/17
1400
【📕分布式锁通关指南 08】源码剖析redisson可重入锁之释放及阻塞与非阻塞获取
Redis分布式锁深入分析
试想一下,在高并发下,redis出现了雪崩,那么你设置了setnx,但是在设置expire之前崩了,呃呃呃~
Karos
2023/06/16
9090
Redis分布式锁深入分析
redisson分布式锁源码和原理浅析
之前写过一篇使用redisson完成简单的分布式锁的文章,https://blog.csdn.net/tianyaleixiaowu/article/details/90036180
天涯泪小武
2019/08/15
2.4K0
redisson分布式锁源码和原理浅析
Redisson实现分布式锁原理
Redisson实现分布式锁原理
Java架构师必看
2021/05/14
1.6K0
Redisson实现分布式锁原理
Redis高并发分布式锁详解
  1.为了解决Java共享内存模型带来的线程安全问题,我们可以通过加锁来保证资源访问的单一,如JVM内置锁synchronized,类级别的锁ReentrantLock。
忧愁的chafry
2022/10/30
1.2K0
Redis高并发分布式锁详解
redisson分布式锁实现原理_redisson连接池
近期在处理程序有两个不同来源入口的时候,因为容易产生并发情况,造成会有脏数据产生,在同事推荐下使用redisson的锁来解决并发问题。 先上使用的一定程度封装的工具类:
全栈程序员站长
2022/11/03
4810
redisson分布式锁实现原理_redisson连接池
纠正误区:这才是 SpringBoot Redis 分布式锁的正确实现方式
在单机部署的时候,我们可以使用 Java 中提供的 JUC 锁机制避免多线程同时操作一个共享变量产生的安全问题。JUC 锁机制只能保证同一个 JVM 进程中的同一时刻只有一个线程操作共享资源。
码哥字节
2024/01/29
1.4K0
纠正误区:这才是 SpringBoot Redis 分布式锁的正确实现方式
Redis进阶学习03---Redis完成秒杀和Redis分布式锁的应用
数据库自增指的是单独使用数据库中某一张表来专门存放主键,当我们需要的时候,只需要提前从该表中读取出一批主键集合,缓存在内存中即可,但是该方法显然太慢了,因此不推荐使用
大忽悠爱学习
2022/05/09
8030
Redis进阶学习03---Redis完成秒杀和Redis分布式锁的应用
Redisson重入锁是通过setnx命令实现的?别再云了
问过很多面试者,redisson的可重复锁是怎么实现的,很多面试者都会不假思索的回答是通过redis的setnx命令来实现的,那么真的是这样吗?今天我们就一起来看下redisson分布式可重入锁到底是怎么实现的。
Java进阶之路
2022/08/03
1.2K0
Redisson重入锁是通过setnx命令实现的?别再云了
分布式锁用Redis坚决不用Zookeeper?
系统 A 是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用户下单。
数据和云
2019/07/30
4.3K1
可靠的分布式锁 RedLock 与 redisson 的实现
但就“高可用”来说,似乎仍然有所欠缺,那就是如果他所依赖的 redis 是单点的,如果发生故障,则整个业务的分布式锁都将无法使用,即便是我们将单点的 redis 升级为 redis 主从模式或集群,对于固定的 key 来说,master 节点仍然是独立存在的,由于存在着主从同步的时间间隔,如果在这期间 master 节点发生故障,slaver 节点被选举为 master 节点,那么,master 节点上存储的分布式锁信息可能就会丢失,从而造成竞争条件。
用户3147702
2022/06/27
5.5K0
可靠的分布式锁 RedLock 与 redisson 的实现
分布式锁用 Redis 还是 Zookeeper?
实际开发中,使用的最多还是Redis和Zookeeper,所以,本文就只聊这两种。
田维常
2022/03/22
3130
分布式锁用 Redis 还是 Zookeeper?
推荐阅读
相关推荐
分布式锁实现大型连续剧之(一):Redis
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验