iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Springboot基于Redisson如何实现Redis分布式可重入锁源码解析
  • 405
分享到

Springboot基于Redisson如何实现Redis分布式可重入锁源码解析

2023-06-29 08:06:07 405人浏览 独家记忆
摘要

这篇文章主要介绍了SpringBoot基于Redisson如何实现Redis分布式可重入锁源码解析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一、前言我们在实现使用Redi

这篇文章主要介绍了SpringBoot基于Redisson如何实现Redis分布式可重入源码解析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

      一、前言

      我们在实现使用Redis实现分布式锁,最开始一般使用SET resource-name anystring NX EX max-lock-time进行加锁,使用lua脚本保证原子性进行实现释放锁。这样手动实现比较麻烦,对此Redis官网也明确说Java版使用Redisson来实现。小编也是看了官网慢慢的摸索清楚,特写此记录一下。从官网到整合springboot到源码解读,以单节点为例。

      二、为什么使用Redisson

      1. 我们打开官网

      redis中文官网

      2. 我们可以看到官方让我们去使用其他

      Springboot基于Redisson如何实现Redis分布式可重入锁源码解析

      3. 打开官方推荐

      Springboot基于Redisson如何实现Redis分布式可重入锁源码解析

      4. 找到文档

      Redisson地址

      Springboot基于Redisson如何实现Redis分布式可重入锁源码解析

      Redisson结构

      Springboot基于Redisson如何实现Redis分布式可重入锁源码解析

      三、Springboot整合Redisson

      1. 导入依赖

      <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency>    <groupId>redis.clients</groupId>    <artifactId>jedis</artifactId></dependency><!--redis分布式锁--><dependency>    <groupId>org.redisson</groupId>    <artifactId>redisson</artifactId>    <version>3.12.0</version></dependency>

      2. 以官网为例查看如何配置

      Springboot基于Redisson如何实现Redis分布式可重入锁源码解析

      3. 编写配置类

      import org.redisson.Redisson;import org.redisson.api.RedissonClient;import org.redisson.config.Config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class MyRedissonConfig {        @Bean(destroyMethod="shutdown")    public RedissonClient redisson(){        // 1. 创建配置        Config config = new Config();        // 一定要加redis://        config.useSingleServer().setAddress("redis://192.168.17.130:6379");        // 2. 根据config创建出redissonClient实例        RedissonClient redissonClient = Redisson.create(config);        return redissonClient;    }}

      4. 官网测试加锁例子

      Springboot基于Redisson如何实现Redis分布式可重入锁源码解析

      5. 根据官网简单Controller接口编写

      @ResponseBody@GetMapping("/hello")public String hello(){    // 1.获取一把锁,只要锁名字一样,就是同一把锁    RLock lock = redisson.getLock("my-lock");    // 2. 加锁    lock.lock();// 阻塞试等待  默认加的都是30s    // 带参数情况    // lock.lock(10, TimeUnit.SECONDS);// 10s自动解锁,自动解锁时间一定要大于业务的执行时间。    try {        System.out.println("加锁成功" + Thread.currentThread().getId());        Thread.sleep(30000);    } catch (InterruptedException e) {        e.printStackTrace();    } finally {        // 3. 解锁        System.out.println("解锁成功:" + Thread.currentThread().getId());        lock.unlock();    }    return "hello";}

      6. 测试

      Springboot基于Redisson如何实现Redis分布式可重入锁源码解析

      四、lock.lock()源码分析

      1. 打开RedissonLock实现类

      Springboot基于Redisson如何实现Redis分布式可重入锁源码解析

      2. 找到实现方法

      @Overridepublic void lock() {    try {    // 我们发现不穿过期时间源码默认过期时间为-1        lock(-1, null, false);    } catch (InterruptedException e) {        throw new IllegalStateException();    }}

      3. 按住Ctrl进去lock方法

      private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {// 获取线程的id,占有锁的时候field的值为UUID:线程号id    long threadId = Thread.currentThread().getId();    // 尝试获得锁    Long ttl = tryAcquire(leaseTime, unit, threadId);    // lock acquired 获得锁,返回    if (ttl == null) {        return;    }// 这里说明获取锁失败,就通过线程id订阅这个锁    RFuture<RedissonLockEntry> future = subscribe(threadId);    if (interruptibly) {        commandExecutor.syncSubscriptionInterrupted(future);    } else {        commandExecutor.syncSubscription(future);    }    try {    // 这里进行自旋,不断尝试获取锁        while (true) {        // 继续尝试获取锁            ttl = tryAcquire(leaseTime, unit, threadId);            // lock acquired 获取成功            if (ttl == null) {            // 直接返回,挑出自旋                break;            }            // waiting for message 继续等待获得锁            if (ttl >= 0) {                try {                    future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);                } catch (InterruptedException e) {                    if (interruptibly) {                        throw e;                    }                    future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);                }            } else {                if (interruptibly) {                    future.getNow().getLatch().acquire();                } else {                    future.getNow().getLatch().acquireUninterruptibly();                }            }        }    } finally {     // 取消订阅        unsubscribe(future, threadId);    }//        get(lockAsync(leaseTime, unit));}

      4. 进去尝试获取锁方法

      Springboot基于Redisson如何实现Redis分布式可重入锁源码解析

      private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {// 直接进入异步方法    return get(tryAcquireAsync(leaseTime, unit, threadId));}private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {    // 这里进行判断如果没有设置参数leaseTime = -1    if (leaseTime != -1) {        return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);    }    // 此方法进行获得锁,过期时间为看门狗的默认时间    // private long lockWatchdogTimeout = 30 * 1000;看门狗默认过期时间为30s    // 加锁和过期时间要保证原子性,这个方法后面肯定调用执行了Lua脚本,我们下面在看    RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);    // 开启一个定时任务进行不断刷新过期时间    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {        if (e != null) {            return;        }        // lock acquired 获得锁        if (ttlRemaining == null) {        // 刷新过期时间方法,我们下一步详细说一下            scheduleExpirationRenewal(threadId);    });    return ttlRemainingFuture;

      5. 查看tryLockInnerAsync()方法

      <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {    internalLockLeaseTime = unit.toMillis(leaseTime);    return commandExecutor.evalWriteAsync(getName(), LonGCodec.INSTANCE, command,      // 首先判断锁是否存在              "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; " +              // hexists查看哈希表的指定字段是否存在,存在锁并且是当前线程持有锁              "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +              // hincrby自增一                  "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +                  // 锁的值大于1,说明是可重入锁,重置过期时间                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +                  "return nil; " +              "end; " +              // 锁已存在,且不是本线程,则返回过期时间ttl              "return redis.call('pttl', KEYS[1]);",                Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));}

      6. 进入4留下的定时任务scheduleExpirationRenewal()方法

      一步步往下找源码:scheduleExpirationRenewal --->renewExpiration

      根据下面源码,定时任务刷新时间为:internalLockLeaseTime / 3,是看门狗的1/3,即为10s刷新一次

      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);}

      五、lock.lock(10, TimeUnit.SECONDS)源码分析

      打开实现类

      @Overridepublic void lock(long leaseTime, TimeUnit unit) {    try {    // 这里的过期时间为我们输入的10        lock(leaseTime, unit, false);    } catch (InterruptedException e) {        throw new IllegalStateException();    }}

      方法lock()实现展示,同三.3源码

      直接来到尝试获得锁tryAcquireAsync()方法

      private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {    // 这里进行判断如果没有设置参数leaseTime = -1,此时我们为10    if (leaseTime != -1) {    // 来到此方法        return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);    }    // 此处省略后面内容,前面以详细说明。。。。}

      打开tryLockInnerAsync()方法

      我们不难发现和没有传过期时间的方法一样,只不过leaseTime的值变了。

      <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {    internalLockLeaseTime = unit.toMillis(leaseTime);    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,      // 首先判断锁是否存在              "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; " +              // hexists查看哈希表的指定字段是否存在,存在锁并且是当前线程持有锁              "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +              // hincrby自增一                  "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +                  // 锁的值大于1,说明是可重入锁,重置过期时间                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +                  "return nil; " +              "end; " +              // 锁已存在,且不是本线程,则返回过期时间ttl              "return redis.call('pttl', KEYS[1]);",                Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));}

      六、lock.unlock()源码分析

      打开方法实现

      @Overridepublic void unlock() {    try {    // 点击进入释放锁方法        get(unlockAsync(Thread.currentThread().getId()));    } catch (RedisException e) {        if (e.getCause() instanceof IllegalMonitorStateException) {            throw (IllegalMonitorStateException) e.getCause();        } else {            throw e;        }    }    //        Future<Void> future = unlockAsync();//        future.awaitUninterruptibly();//        if (future.isSuccess()) {//            return;//        }//        if (future.cause() instanceof IllegalMonitorStateException) {//            throw (IllegalMonitorStateException)future.cause();//        }//        throw commandExecutor.convertException(future);}

      打开unlockAsync()方法

      @Overridepublic RFuture<Void> unlockAsync(long threadId) {    RPromise<Void> result = new RedissonPromise<Void>();    // 解锁方法,后面展开说    RFuture<Boolean> future = unlockInnerAsync(threadId);// 完成    future.onComplete((opStatus, e) -> {        if (e != null) {        // 取消到期续订            cancelExpirationRenewal(threadId);            // 将这个未来标记为失败并通知所有人            result.tryFailure(e);            return;        }// 状态为空,说明解锁的线程和当前锁不是同一个线程        if (opStatus == null) {            IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "                    + id + " thread-id: " + threadId);            result.tryFailure(cause);            return;        }                cancelExpirationRenewal(threadId);        result.trySuccess(null);    });    return result;}

      打开unlockInnerAsync()方法

      protected RFuture<Boolean> unlockInnerAsync(long threadId) {    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,    // 判断释放锁的线程和已存在锁的线程是不是同一个线程,不是返回空            "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +                "return nil;" +            "end; " +            // 释放锁后,加锁次数减一            "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +            // 判断剩余数量是否大于0            "if (counter > 0) then " +            // 大于0 ,则刷新过期时间                "redis.call('pexpire', KEYS[1], ARGV[2]); " +                "return 0; " +            "else " +            // 释放锁,删除key并发布锁释放的消息                "redis.call('del', KEYS[1]); " +                "redis.call('publish', KEYS[2], ARGV[1]); " +                "return 1; "+            "end; " +            "return nil;",            Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));}

      感谢你能够认真阅读完这篇文章,希望小编分享的“Springboot基于Redisson如何实现Redis分布式可重入锁源码解析”这篇文章对大家有帮助,同时也希望大家多多支持编程网,关注编程网精选频道,更多相关知识等着你来学习!

      --结束END--

      本文标题: Springboot基于Redisson如何实现Redis分布式可重入锁源码解析

      本文链接: https://www.lsjlt.com/news/323882.html(转载时请注明来源链接)

      有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

      本篇文章演示代码以及资料文档资料下载

      下载Word文档到电脑,方便收藏和打印~

      下载Word文档
      猜你喜欢
      • Springboot基于Redisson实现Redis分布式可重入锁源码解析
        目录一、前言二、为什么使用Redisson1. 我们打开官网2. 我们可以看到官方让我们去使用其他3. 打开官方推荐4. 找到文档三、Springboot整合Redisson1. 导...
        99+
        2024-04-02
      • Springboot基于Redisson如何实现Redis分布式可重入锁源码解析
        这篇文章主要介绍了Springboot基于Redisson如何实现Redis分布式可重入锁源码解析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一、前言我们在实现使用Redi...
        99+
        2023-06-29
      • redisson实现分布式锁的源码解析
        目录redisson测试代码加锁设计锁续期设计锁的自旋重试解锁设计撤销锁续期解锁成功唤排队线程 redisson redisson 实现分布式锁的机制如下: 依赖版本 implem...
        99+
        2024-04-02
      • redis分布式锁之可重入锁的实现代码
        上篇redis实现的分布式锁,有一个问题,它不可重入。 所谓不可重入锁,即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞。 同一个人拿一个锁 ...
        99+
        2024-04-02
      • 如何理解Redisson分布式锁的源码
        本篇内容介绍了“如何理解Redisson分布式锁的源码”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Red...
        99+
        2024-04-02
      • 如何分析基于redis分布式锁实现秒杀
        本篇文章为大家展示了如何分析基于redis分布式锁实现秒杀,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。业务场景所谓秒杀,从业务角度看,是短时间内多个用户“争抢”资源,这里的资源在大部分秒杀场景里是...
        99+
        2023-06-02
      • SpringBoot整合Redisson如何实现分布式锁
        这篇文章将为大家详细讲解有关SpringBoot整合Redisson如何实现分布式锁,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Redisson是架设在redis基础上的一个Java驻内存数据网格(In...
        99+
        2023-06-25
      • Springboot中如何使用Redisson实现分布式锁浅析
        目录前言1. 概述2. Redisson 在 Springboot 中的使用2.1 引入依赖2.2 在 Springboot 配置中配置Redis2.3 Demo代码3. 综述前言 ...
        99+
        2024-04-02
      • 详解基于redis实现分布式锁
        目录前言原理剖析实现编写注解拦截器拦截上述提及工具问题分析前言 为了保证一个在高并发存场景下只能被同一个线程操作,java并发处理提供ReentrantLock或Synchroniz...
        99+
        2024-04-02
      • 基于Redis分布式锁的实现代码
        概述 目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(...
        99+
        2024-04-02
      • SpringBoot+Redis如何实现分布式锁
        这篇文章主要介绍了SpringBoot+Redis如何实现分布式锁,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。jedis的nx生成锁 如何删除锁 模拟抢...
        99+
        2023-06-16
      • SpringBoot基于Redis的分布式锁实现过程记录
        目录一、概述二、环境搭建三、模拟一个库存扣减的场景四、总结一、概述 什么是分布式锁 在单机环境中,一般在多并发多线程场景下,出现多个线程去抢占一个资源,这个时候会出现线程同步问题,造...
        99+
        2024-04-02
      • 基于redis分布式锁如何实现秒杀功能
        这篇文章主要介绍了基于redis分布式锁如何实现秒杀功能,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。业务场景所谓秒杀,从业务角度看,是短时...
        99+
        2024-04-02
      • java基于jedisLock—redis分布式锁实现示例代码
        分布式锁是啥?单机锁的概念:我们正常跑的单机项目(也就是在tomcat下跑一个项目不配置集群)想要在高并发的时候加锁很容易就可以搞定,java提供了很多的机制例如:synchronized、volatile、ReentrantLock等锁的...
        99+
        2023-05-30
        jedislock redis 分布式锁
      • 如何理解分布式系统下基于Redis的分布式锁
        这篇文章主要介绍“如何理解分布式系统下基于Redis的分布式锁”,在日常操作中,相信很多人在如何理解分布式系统下基于Redis的分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大...
        99+
        2024-04-02
      • SpringBoot之如何使用Redis实现分布式锁
        小编给大家分享一下SpringBoot之如何使用Redis实现分布式锁,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!springboot是什么springboot一种全新的编程规范,其设计目的是用来简化新Spring应用的...
        99+
        2023-06-14
      • Redis如何实现分布式锁详解
        目录一、前言二、实现原理2.1 加锁2.2 解锁三、通过RedisTemplate实现分布式锁四、通过Redisson实现一、前言 在Java的并发编程中,我们通过锁,来避免由于竞争...
        99+
        2024-04-02
      • 如何使用自定义注解实现redisson分布式锁
        这篇文章主要讲解了“如何使用自定义注解实现redisson分布式锁”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何使用自定义注解实现redisson分布式锁”吧!自定义注解实现rediss...
        99+
        2023-06-29
      • 如何利用Redis实现分布式锁的高可用
        如何利用Redis实现分布式锁的高可用,需要具体代码示例一、引言在分布式系统中,由于多个进程或线程可以同时访问共享资源,会带来资源竞争的问题。为了解决这个问题,需要引入分布式锁来进行资源的互斥访问。Redis作为一种内存数据库,提供了分布式...
        99+
        2023-11-07
        高可用 redis 分布式锁
      • 如何使用注解方式实现 Redis 分布式锁
        目录引入 Redisson初始化 Redisson编写 Redisson 分布式锁工具类声明注解 @Lock注解解析类引入 Redisson <dependency> ...
        99+
        2024-04-02
      软考高级职称资格查询
      编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
      • 官方手机版

      • 微信公众号

      • 商务合作