精华内容
下载资源
问答
  • redisson实现分布式锁

    2020-12-16 09:43:22
    redisson实现分布式锁,基于aop注解形式使用。
  • Redisson实现分布式锁

    2021-01-04 09:09:45
    Redisson实现分布式锁(1)原理 有关Redisson作为实现分布式锁,总的分3大模块来讲。 1、Redisson实现分布式锁原理 2、Redisson实现分布式锁的源码解析 3、Redisson实现分布式锁的项目代码(可以用于实际项目中) ...

    Redisson实现分布式锁(1)原理

    有关Redisson作为实现分布式锁,总的分3大模块来讲。

    1、Redisson实现分布式锁原理
    2、Redisson实现分布式锁的源码解析
    3、Redisson实现分布式锁的项目代码(可以用于实际项目中)
    

    本文只介绍Redisson如何实现分布式锁的原理。其它的会在接下来的博客讲,最后有关Redisson实现分布式锁的项目代码的博客中会放上项目源码到GitHub上。

    一、高效分布式锁

    当我们在设计分布式锁的时候,我们应该考虑分布式锁至少要满足的一些条件,同时考虑如何高效的设计分布式锁,这里我认为以下几点是必须要考虑的。

    1、互斥

    在分布式高并发的条件下,我们最需要保证,同一时刻只能有一个线程获得锁,这是最基本的一点。

    2、防止死锁

    在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来得及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。

    所以分布式非常有必要设置锁的有效时间,确保系统出现故障后,在一定时间内能够主动去释放锁,避免造成死锁的情况。

    3、性能

    对于访问量大的共享资源,需要考虑减少锁等待的时间,避免导致大量线程阻塞。

    所以在锁的设计时,需要考虑两点。

    1、锁的颗粒度要尽量小。比如你要通过锁来减库存,那这个锁的名称你可以设置成是商品的ID,而不是任取名称。这样这个锁只对当前商品有效,锁的颗粒度小。

    2、锁的范围尽量要小。比如只要锁2行代码就可以解决问题的,那就不要去锁10行代码了。

    4、重入

    我们知道ReentrantLock是可重入锁,那它的特点就是:同一个线程可以重复拿到同一个资源的锁。重入锁非常有利于资源的高效利用。关于这点之后会做演示。

    针对以上Redisson都能很好的满足,下面就来分析下它。

    二、Redisson原理分析

    为了更好的理解分布式锁的原理,画张图来分析。
    在这里插入图片描述

    1、加锁机制

    线程去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。

    线程去获取锁,获取失败: 一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis数据库。

    2、watch dog自动延期机制

    在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放,你也可以设置锁的有效时间(不设置默认30秒),这样的目的主要是防止死锁的发生。

    但在实际开发中会有下面一种情况:

    //设置锁1秒过去
    redissonLock.lock("redisson", 1);
    /**
    * 业务逻辑需要咨询2秒
    */
    redissonLock.release("redisson");
    
    /**
    * 线程1 进来获得锁后,线程一切正常并没有宕机,但它的业务逻辑需要执行2秒,这就会有个问题,在 线程1 执行1秒后,这个锁就自动过期了,
    * 那么这个时候 线程2 进来了。那么就存在 线程1和线程2 同时在这段业务逻辑里执行代码,这当然是不合理的。
    * 而且如果是这种情况,那么在解锁时系统会抛异常,因为解锁和加锁已经不是同一线程了,具体后面代码演示。
    */
    

    所以这个时候看门狗就出现了,它的作用就是 线程1 业务还没有执行完,时间就过了,线程1 还想持有锁的话,就会启动一个watch dog后台线程,不断的延长锁key的生存时间。

    注意 正常这个看门狗线程是不启动的,还有就是这个看门狗启动后对整体性能也会有一定影响,所以不建议开启看门狗。

    3、为啥要用lua脚本呢?

    这个不用多说,主要是如果你的业务逻辑复杂的话,通过封装在lua脚本中发送给redis,而且redis是单线程的,这样就保证这段复杂业务逻辑执行的原子性

    4、可重入加锁机制

    Redisson可以实现可重入加锁机制的原因,有两点:

    1、Redis存储锁的数据类型是 Hash类型
    2、Hash数据类型的key值包含了当前线程信息。
    

    下面是redis存储的数据
    在这里插入图片描述
    这里表面数据类型是Hash类型,Hash类型相当于我们java的 <key,<key1,value>> 类型,这里key是指 ‘redisson’

    它的有效期还有9秒,我们再来看里们的key1值为078e44a3-5f95-4e24-b6aa-80684655a15a:45它的组成是:

    guid + 当前线程的ID。后面的value是就和可重入加锁有关。

    举图说明
    在这里插入图片描述

    上面这图的意思就是可重入锁的机制,它最大的优点就是相同线程不需要在等待锁,而是可以直接进行相应操作。

    5、Redis分布式锁的缺点

    Redis分布式锁会有个缺陷,就是在Redis哨兵模式下:

    客户端1 对某个master节点写入了redisson锁,此时会异步复制给对应的 slave节点。但是这个过程中一旦发生 master节点宕机,主备切换,slave节点从变为了 master节点。

    这时客户端2 来尝试加锁的时候,在新的master节点上也能加锁,此时就会导致多个客户端对同一个分布式锁完成了加锁。

    这时系统在业务语义上一定会出现问题,导致各种脏数据的产生

    缺陷在哨兵模式或者主从模式下,如果 master实例宕机的时候,可能导致多个客户端同时完成加锁。

    Redisson实现分布式锁(2)—RedissonLock

    Redisson分布式锁的实现是基于RLock接口,RedissonLock实现RLock接口。

    一、RLock接口

    1、概念

    public interface RLock extends Lock, RExpirable, RLockAsync
    

    很明显RLock是继承Lock锁,所以他有Lock锁的所有特性,比如lock、unlock、trylock等特性,同时它还有很多新特性:强制锁释放,带有效期的锁,。

    2、RLock锁API

    这里针对上面做个整理,这里列举几个常用的接口说明

    public interface RRLock {
        //----------------------Lock接口方法-----------------------
    
        /**
         * 加锁 锁的有效期默认30秒
         */
        void lock();
        /**
         * tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false .
         */
        boolean tryLock();
        /**
         * tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,
         * 在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
         *
         * @param time 等待时间
         * @param unit 时间单位 小时、分、秒、毫秒等
         */
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
        /**
         * 解锁
         */
        void unlock();
        /**
         * 中断锁 表示该锁可以被中断 假如A和B同时调这个方法,A获取锁,B为获取锁,那么B线程可以通过
         * Thread.currentThread().interrupt(); 方法真正中断该线程
         */
        void lockInterruptibly();
    
        //----------------------RLock接口方法-----------------------
        /**
         * 加锁 上面是默认30秒这里可以手动设置锁的有效时间
         *
         * @param leaseTime 锁有效时间
         * @param unit      时间单位 小时、分、秒、毫秒等
         */
        void lock(long leaseTime, TimeUnit unit);
        /**
         * 这里比上面多一个参数,多添加一个锁的有效时间
         *
         * @param waitTime  等待时间
         * @param leaseTime 锁有效时间
         * @param unit      时间单位 小时、分、秒、毫秒等
         */
        boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
        /**
         * 检验该锁是否被线程使用,如果被使用返回True
         */
        boolean isLocked();
        /**
         * 检查当前线程是否获得此锁(这个和上面的区别就是该方法可以判断是否当前线程获得此锁,而不是此锁是否被线程占有)
         * 这个比上面那个实用
         */
        boolean isHeldByCurrentThread();
        /**
         * 中断锁 和上面中断锁差不多,只是这里如果获得锁成功,添加锁的有效时间
         * @param leaseTime  锁有效时间
         * @param unit       时间单位 小时、分、秒、毫秒等
         */
        void lockInterruptibly(long leaseTime, TimeUnit unit);  
    }
    

    RLock相关接口,主要是新添加了 leaseTime 属性字段,主要是用来设置锁的过期时间,避免死锁。

    二、RedissonLock实现类

    public class RedissonLock extends RedissonExpirable implements RLock
    

    RedissonLock实现了RLock接口,所以实现了接口的具体方法。这里我列举几个方法说明下

    1、void lock()方法

    @Override
    public void lock() {
        try {
            lockInterruptibly();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    

    发现lock锁里面进去其实用的是lockInterruptibly(中断锁,表示可以被中断),而且捕获异常后用 Thread.currentThread().interrupt()来真正中断当前线程,其实它们是搭配一起使用的。

    具体有关lockInterruptibly()方法讲解推荐一个博客。博客Lock的lockInterruptibly()

    接下来执行流程,这里理下关键几步

       /**
         * 1、带上默认值调另一个中断锁方法
         */
        @Override
        public void lockInterruptibly() throws InterruptedException {
            lockInterruptibly(-1, null);
        }
        /**
         * 2、另一个中断锁的方法
         */
        void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException 
        /**
         * 3、这里已经设置了锁的有效时间默认为30秒  (commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout()=30)
         */
        RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
        /**
         * 4、最后通过lua脚本访问Redis,保证操作的原子性
         */
        <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; " +
                            "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.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
        }
    

    那么void lock(long leaseTime, TimeUnit unit)方法其实和上面很相似了,就是从上面第二步开始的。

    2、tryLock(long waitTime, long leaseTime, TimeUnit unit)

    接口的参数和含义上面已经说过了,现在我们开看下源码,这里只显示一些重要逻辑。

    @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(leaseTime, unit, threadId);
            //1、 获取锁同时获取成功的情况下,和lock(...)方法是一样的 直接返回True,获取锁False再往下走
            if (ttl == null) {
                return true;
            }
            //2、如果超过了尝试获取锁的等待时间,当然返回false 了。
            time -= System.currentTimeMillis() - current;
            if (time <= 0) {
                acquireFailed(threadId);
                return false;
            }
    
            // 3、订阅监听redis消息,并且创建RedissonLockEntry,其中RedissonLockEntry中比较关键的是一个 Semaphore属性对象,用来控制本地的锁请求的信号量同步,返回的是netty框架的Future实现。
            final RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
            //  阻塞等待subscribe的future的结果对象,如果subscribe方法调用超过了time,说明已经超过了客户端设置的最大wait time,则直接返回false,取消订阅,不再继续申请锁了。
            //  只有await返回true,才进入循环尝试获取锁
            if (!await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {
                if (!subscribeFuture.cancel(false)) {
                    subscribeFuture.addListener(new FutureListener<RedissonLockEntry>() {
                        @Override
                        public void operationComplete(Future<RedissonLockEntry> future) throws Exception {
                            if (subscribeFuture.isSuccess()) {
                                unsubscribe(subscribeFuture, threadId);
                            }
                        }
                    });
                }
                acquireFailed(threadId);
                return false;
            }
    
           //4、如果没有超过尝试获取锁的等待时间,那么通过While一直获取锁。最终只会有两种结果
            //1)、在等待时间内获取锁成功 返回true。2)等待时间结束了还没有获取到锁那么返回false。
            while (true) {
                long currentTime = System.currentTimeMillis();
                ttl = tryAcquire(leaseTime, unit, threadId);
                // 获取锁成功
                if (ttl == null) {
                    return true;
                }
               //   获取锁失败
                time -= System.currentTimeMillis() - currentTime;
                if (time <= 0) {
                    acquireFailed(threadId);
                    return false;
                }
            }
        }
    

    重点 tryLock一般用于特定满足需求的场合,但不建议作为一般需求的分布式锁,一般分布式锁建议用void lock(long leaseTime, TimeUnit unit)。因为从性能上考虑,在高并发情况下后者效率是前者的好几倍

    3、unlock()

    解锁的逻辑很简单。

    @Override
        public void unlock() {
            // 1.通过 Lua 脚本执行 Redis 命令释放锁
            Boolean opStatus = commandExecutor.evalWrite(getName(), LongCodec.INSTANCE,
                    RedisCommands.EVAL_BOOLEAN,
                    "if (redis.call('exists', KEYS[1]) == 0) then " +
                            "redis.call('publish', KEYS[2], ARGV[1]); " +
                            "return 1; " +
                            "end;" +
                            "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                            "return nil;" +
                            "end; " +
                            "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                            "if (counter > 0) then " +
                            "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                            "return 0; " +
                            "else " +
                            "redis.call('del', KEYS[1]); " +
                            "redis.call('publish', KEYS[2], ARGV[1]); " +
                            "return 1; "+
                            "end; " +
                            "return nil;",
                    Arrays.<Object>asList(getName(), getChannelName()),
                    LockPubSub.unlockMessage, internalLockLeaseTime,
                    getLockName(Thread.currentThread().getId()));
            // 2.非锁的持有者释放锁时抛出异常
            if (opStatus == null) {
                throw new IllegalMonitorStateException(
                        "attempt to unlock lock, not locked by current thread by node id: "
                                + id + " thread-id: " + Thread.currentThread().getId());
            }
            // 3.释放锁后取消刷新锁失效时间的调度任务
            if (opStatus) {
                cancelExpirationRenewal();
            }
        }
    

    使用 EVAL 命令执行 Lua 脚本来释放锁:

    1. key 不存在,说明锁已释放,直接执行 publish 命令发布释放锁消息并返回 1
    2. key 存在,但是 field 在 Hash 中不存在,说明自己不是锁持有者,无权释放锁,返回 nil
    3. 因为锁可重入,所以释放锁时不能把所有已获取的锁全都释放掉,一次只能释放一把锁,因此执行 hincrby 对锁的值减一
    4. 释放一把锁后,如果还有剩余的锁,则刷新锁的失效时间并返回 0;如果刚才释放的已经是最后一把锁,则执行 del 命令删除锁的 key,并发布锁释放消息,返回 1

    注意这里有个实际开发过程中,容易出现很容易出现上面第二步异常,非锁的持有者释放锁时抛出异常。比如下面这种情况

    //设置锁1秒过去
    redissonLock.lock("redisson", 1);
    /**
    * 业务逻辑需要咨询2秒
    */
    redissonLock.release("redisson");
    /**
    * 线程1 进来获得锁后,线程一切正常并没有宕机,但它的业务逻辑需要执行2秒,这就会有个问题,在 线程1 执行1秒后,这个锁就自动过期了,
    * 那么这个时候 线程2 进来了。在线程1去解锁就会抛上面这个异常(因为解锁和当前锁已经不是同一线程了)
    */
    

    Redisson实现分布式锁(3)—项目落地实现

    一、项目概述

    1、技术架构

    项目总体技术选型

    SpringBoot2.1.5 + Maven3.5.4 + Redisson3.5.4 + lombok(插件)
    

    2、加锁方式

    该项目支持 自定义注解加锁常规加锁 两种模式

    自定义注解加锁

    @DistributedLock(value="goods", leaseTime=5)
    public String lockDecreaseStock(){
        //业务逻辑
    }
    

    常规加锁

    //1、加锁
    redissonLock.lock("redisson", 10);
    //2、业务逻辑
    //3、解锁
    redissonLock.unlock("redisson");
    

    3、Redis部署方式

    该项目支持四种Redis部署方式

    1、单机模式部署
    2、集群模式部署
    3、主从模式部署
    4、哨兵模式部署
    

    该项目已经实现支持上面四种模式,你要采用哪种只需要修改配置文件application.properties,项目代码不需要做任何修改。

    4、项目整体结构

    redis-distributed-lock-core # 核心实现
    |
    ---src
          |
          ---com.jincou.redisson
                               |# 通过注解方式 实现分布式锁
                               ---annotation
                               |# 配置类实例化RedissonLock
                               ---config
                               |# 放置常量信息
                               ---constant
                               |# 读取application.properties信息后,封装到实体
                               ---entity    
                               |# 支持单机、集群、主从、哨兵 代码实现
                               ---strategy
    
    redis-distributed-lock-web-test # 针对上面实现类的测试类
    |
    ---src
          |
          ---java
                |
                ---com.jincou.controller
                                     |# 测试 基于注解方式实现分布式锁
                                     ---AnnotatinLockController.java
                                     |# 测试 基于常规方式实现分布式锁
                                     ---LockController.java
          ---resources                
               | # 配置端口号 连接redis信息(如果确定部署类型,那么将连接信息放到core项目中)
                ---application.properties
    

    二、测试

    模拟1秒内100个线程请求接口,来测试结果是否正确。同时测试3中不同的锁:lock锁、trylock锁、注解锁。

    1、lock锁

    /**
    * 模拟这个是商品库存
    */
    public static volatile Integer TOTAL = 10;
    
    @GetMapping("lock-decrease-stock")
    public String lockDecreaseStock() throws InterruptedException {
        redissonLock.lock("lock", 10);
        if (TOTAL > 0) {
            TOTAL--;
        }
        Thread.sleep(50);
        log.info("======减完库存后,当前库存===" + TOTAL);
        //如果该线程还持有该锁,那么释放该锁。如果该线程不持有该锁,说明该线程的锁已到过期时间,自动释放锁
        if (redissonLock.isHeldByCurrentThread("lock")) {
            redissonLock.unlock("lock");
        }
        return "=================================";
    }
    

    压测结果
    在这里插入图片描述
    没问题,不会超卖!

    2、tryLock锁

    /**
    * 模拟这个是商品库存
    */
    public static volatile Integer TOTAL = 10;
    
    @GetMapping("trylock-decrease-stock")
    public String trylockDecreaseStock() throws InterruptedException {
        if (redissonLock.tryLock("trylock", 10, 5)) {
            if (TOTAL > 0) {
                TOTAL--;
            }
            Thread.sleep(50);
            redissonLock.unlock("trylock");
            log.info("====tryLock===减完库存后,当前库存===" + TOTAL);
        } else {
            log.info("[ExecutorRedisson]获取锁失败");
        }
        return "===================================";
    }
    

    测试结果
    在这里插入图片描述
    没有问题 ,不会超卖!

    3、注解锁

    /**
    * 模拟这个是商品库存
    */
    public static volatile Integer TOTAL = 10;
    
    @GetMapping("annotatin-lock-decrease-stock")
    @DistributedLock(value="goods", leaseTime=5)
    public String lockDecreaseStock() throws InterruptedException {
        if (TOTAL > 0) {
            TOTAL--;
        }
        log.info("===注解模式=== 减完库存后,当前库存===" + TOTAL);
        return "=================================";
    }
    

    测试结果
    在这里插入图片描述
    没有问题 ,不会超卖!

    通过实验可以看出,通过这三种模式都可以实现分布式锁,然后呢?哪个最优。

    三、三种锁的选择

    观点 最完美的就是lock锁,因为

    1、tryLock锁是可能会跳过减库存的操作,因为当过了等待时间还没有获取锁,就会返回false,这显然很致命!
    2、注解锁只能用于方法上,颗粒度太大,满足不了方法内加锁。
    

    1、lock PK tryLock 性能的比较

    模拟5秒内1000个线程分别去压测这两个接口,看报告结果!

    1)lock锁

    压测结果 1000个线程平均响应时间为31324。吞吐量 14.7/sec
    在这里插入图片描述

    2)tryLock锁

    压测结果 1000个线程平均响应时间为28628。吞吐量 16.1/sec
    在这里插入图片描述
    这里只是单次测试,有很大的随机性。从当前环境单次测试来看,tryLock稍微高点。

    2、常见异常 attempt to unlock lock, not ······

    在使用RedissonLock锁时,很容易报这类异常,比如如下操作

    //设置锁1秒过去
    redissonLock.lock("redisson", 1);
    /**
    * 业务逻辑需要咨询2秒
    */
    redissonLock.release("redisson");
    

    上面在并发情况下就会这样
    在这里插入图片描述
    造成异常原因:

    线程1 进来获得锁后,但它的业务逻辑需要执行2秒,在线程1执行1秒后,这个锁就自动过期了,那么这个时候 线程2进来了获得了锁。在线程1去解锁就会抛上面这个异常(因为解锁和当前锁已经不是同一线程了)
    

    所以我们需要注意,设置锁的过期时间不能设置太小,一定要合理,宁愿设置大点。

    正对上面的异常,可以通过isHeldByCurrentThread()方法,

    //如果为false就说明该线程的锁已经自动释放,无需解锁
    if (redissonLock.isHeldByCurrentThread("lock")) {
        redissonLock.unlock("lock");
    }
    

    好了,这篇博客就到这了!

    解决SpringBoot项目repackage failed: Unable to find main class Maven打包 install的问题

    部署这个项目的时候遇到的问题:

    一个通用的工具类项目,在进行打包时,出现repackage failed: Unable to find main class提示找不到主类问题,这个项目是一个通用的工具类,不是一个SpringBoot Web工程,根本启动类哪里来的main方法,到底是什么原因导致这个问题,后来看了一下依赖的父项目的pom文件,

    发现父项目有一个打包的插件

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    

    这时,问题就出现了,报打包失败错误!!!

    解决:如果你的项目是一个放置通用工具类的工程,那么该项目中,就不能包括上面这个打包插件,如果你这个工具类工程依赖有父工程,那么父工程中也不能包括该打包插件,只有你的项目是一个web项目时,含有Main方法的程序入口类,要加该打包插件,放在了父工程的pom文件中,那就是代表了所有子模块都有这个打包插件,所以报错,解决就是去掉这个插件 ,只在web工程中加入这个打包插件!

    代码地址:https://github.com/kongfanyu/spring-boot-distributed-redisson

    展开全文
  • 本文将详细介绍redisson实现分布式锁原理。具有很好的参考价值,下面跟着小编一起来看下吧
  • Redisson 实现分布式锁

    2021-03-06 09:03:44
    Redisson 分布式锁

    Java提供的内置锁机制只在单机系统里有效,在分布式系统,一般多机部署的环境下无法保证线程安全,这时需要在整个系统提供一个全局唯一的锁。

    可以通过Redis,ZooKeeper,数据库实现。

    Redis提供了一个分布式锁的开源框架 Redisson,提供持有锁的默认时间,每隔一段时间自动续期,到期自动删除,不会有死锁发生,使用非常方便。

    1、maven依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.13.4</version>
    </dependency>

    2、配置文件和Redisson配置类

    Redis的单机模式的配置文件和配置类:

    redisson.properties配置文件里添加属性:

    redisson.addressUrl=redis://127.0.0.1:6379

    配置类:

    @Configuration
    @PropertySource(value = "redisson.properties", encoding = "utf-8")
    @ConfigurationProperties(prefix="redisson")
    public class RedissonConfig {
        private String addressUrl;
        public String getAddressUrl() {
            return addressUrl;
        }
        public void setAddressUrl(String addressUrl) {
            this.addressUrl = addressUrl;
        }
    
        @Bean
        public RedissonClient redissonClient() {
            Config config = new Config();
            config.useSingleServer().setAddress(addressUrl);
            return Redisson.create(config);
        }
    }
    

    集群模式配置文件和配置类:

    redisson.cluster=peer1:7001,peer1:7002,peer1:7003,peer1:7004,peer1:7005,peer1:7006

    配置类:

    @Configuration
    @PropertySource(value = "redisson.properties", encoding = "utf-8")
    @ConfigurationProperties(prefix="redisson")
    public class RedissionClusterConfiguration {
        private  String cluster;
    
        public String getCluster() {
            return cluster;
        }
    
        public void setCluster(String cluster) {
            this.cluster = cluster;
        }
    
        @Bean
        public RedissonClient redissonClient(){
            String[] nodes = cluster.split(",");
            for (int i = 0; i< nodes.length; i++){
                nodes[i] = "redis://" + nodes[i];
            }
            Config config = new Config();
            config.useClusterServers()
                    .setScanInterval(2000)
                    .addNodeAddress(nodes);
            return Redisson.create(config);
        }
    }

    3、Redisson 可重入锁的使用,定义一个Service类,接口省略,

    import com.example.redission.demo.Service.SellService;
    import lombok.extern.slf4j.Slf4j;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    
    
    @Slf4j
    @Service
    public class SellServiceImpl implements SellService {
        private Long carCount = 500L;
    
        @Resource
        private RedissonClient redissonClient;
    
        @Override
        public void buyCar() {
            RLock lock = redissonClient.getLock("car");
            lock.lock();
            log.info("lock car");
            if (carCount > 0) {
                --carCount;
                log.info("car count " + carCount);
            } else {
                log.info("stock is clear");
            }
            lock.unlock();
            log.info("unlock car");
        }
    }
    lock.lock() 方法
    如果不设定锁定时间,默认30秒,也可以通过Config.lockWatchdogTimeout 指定
    如果业务执行时间比较长,超过30秒的1/3,即10s后自动续期为30秒

    4、定义一个API接口,

    import com.example.redission.demo.Service.SellService;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    @RestController
    public class SellController {
    
        @Resource
        private SellService sellService;
    
        @GetMapping("buyCar")
        public String buyCar() {
            sellService.buyCar();
            return "buyCar";
        }
    }
    

    5、测试接口是否成功,

    http://localhost:8080/buyCar

     

    6、JMeter压力测试,线程设置如下:

     

    7、查看IDEA 控制台输出

     

    展开全文
  • RedisSon实现分布式锁

    2020-03-17 21:26:52
    3、使用RedisSon构建分布式锁,在需要使用分布式锁的地方注入RedissonClient这个类来获取锁 第一步、引入依赖: <parent> <groupId>org.springframework.boot</groupId> <ar...

    主要步骤:
    1、引入RedisSon的依赖
    2、配置RedisSon的配置类
    3、使用RedisSon构建分布式锁,在需要使用分布式锁的地方注入RedissonClient这个类来获取锁

    第一步、引入依赖:

     <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.5.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--redisson-->
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.6.5</version>
            </dependency>
        </dependencies>

    第二步:RedisSon的配置类

    @Configuration
    public class RedisSonConfig {

        @Value("${spring.redis.host}")
        private String host;

        @Value("${spring.redis.port}")
        private String port;

        @Value("${spring.redis.password}")
        private String password;

        @Bean
        public RedissonClient redissonClient() {
            Config config = new Config();
            config.useSingleServer().setAddress("redis://" + host + ":" + port);
           // config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password) redis 有密码的时候需要设置;
            return Redisson.create(config);
        }

    }
    第三步:使用分布式锁

    @RestController
    public class TestRedisSonController {
        @Autowired
        RedissonClient  redissonClient;
        
        @GetMapping("testRedisSon")
        public String testRedisSon() {{
            RLock lock = redissonClient.getLock("testRedissonLock");
            boolean locked = false;
            try {
                locked = lock.tryLock(0, 10, TimeUnit.SECONDS);
                if (locked) {
                    Thread.sleep(30000);
                    return "ok.......................................";
                } else {
                    return "获取锁失败.......................................";
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                return "获取锁异常.......................................";
            } finally {
                if (!locked) {
                    return "获取锁失败";
                }
                lock.unlock();
            }
        }}

    }
     

    展开全文
  • 主要介绍了Java redisson实现分布式锁原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主要介绍了Java编程redisson实现分布式锁代码示例,小编觉得还是比较不错的,这里给大家分享下,供需要的朋友参考。
  • Spring整合redisson实现分布式锁,用到的redisson-all-2.10.5.jar
  • redisson 实现分布式锁

    2021-01-18 15:59:45
    1.引入redisson依赖 <dependency> <groupId>org.redisson</groupId> <...redisson<...2.自定义注解实现redisson分布式锁 /** * @Description: 基于注解的分布式式锁 *

    1.引入redisson依赖

    	<dependency>
    			<groupId>org.redisson</groupId>
    			<artifactId>redisson</artifactId>
    			<version>3.5.4</version>
       </dependency>

    2.自定义注解实现redisson分布式锁

    /**
     * @Description: 基于注解的分布式式锁
     * @author lz
     */
    @Documented
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface DistributedLock {
    
        /**
         * 锁的名称
         */
        String value() default "redisson";
    
        /**
         * 锁的有效时间
         */
        int leaseTime() default 10;
    }
    

    3. Redisson分布式锁注解解析器

    /**
     * @Description: Redisson分布式锁注解解析器
     * @author lz
     */
    @Aspect
    @Component
    @Slf4j
    public class DistributedLockHandler {
    
        @Autowired
        RedissonLock redissonLock;
    
    
        @Around("@annotation(distributedLock)")
        public void around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {
            log.info("[开始]执行RedisLock环绕通知,获取Redis分布式锁开始");
            //获取锁名称
            String lockName = distributedLock.value();
            //获取超时时间,默认10秒
            int leaseTime = distributedLock.leaseTime();
            redissonLock.lock(lockName, leaseTime);
            try {
                log.info("获取Redis分布式锁[成功],加锁完成,开始执行业务逻辑...");
                joinPoint.proceed();
            } catch (Throwable throwable) {
                log.error("获取Redis分布式锁[异常],加锁失败", throwable);
                throwable.printStackTrace();
            } finally {
                //如果该线程还持有该锁,那么释放该锁。如果该线程不持有该锁,说明该线程的锁已到过期时间,自动释放锁
                if (redissonLock.isHeldByCurrentThread(lockName)) {
                    redissonLock.unlock(lockName);
                }
            }
            log.info("释放Redis分布式锁[成功],解锁完成,结束业务逻辑...");
        }
    }
    

    4.针对源码Redisson进行一层封装

    /**
     * @Description: 针对源码Redisson进行一层封装
     * @author lz
     */
    @Slf4j
    public class RedissonLock {
    
    
      private Redisson redisson;
    
    
        public RedissonLock(RedissonManager redissonManager) {
            this.redissonManager = redissonManager;
            this.redisson = redissonManager.getRedisson();
        }
    
        public RedissonLock() {}
    
        /**
         * 加锁操作 (设置锁的有效时间)
         * @param lockName 锁名称
         * @param leaseTime  锁有效时间
         */
        public void lock(String lockName, long leaseTime) {
            RLock rLock = redisson.getLock(lockName);
            rLock.lock(leaseTime,TimeUnit.SECONDS);
        }
    
        /**
         * 加锁操作 (锁有效时间采用默认时间30秒)
         * @param lockName 锁名称
         */
        public void lock(String lockName) {
            RLock rLock = redisson.getLock(lockName);
            rLock.lock();
        }
    
        /**
         * 加锁操作(tryLock锁,没有等待时间)
         * @param lockName  锁名称
         * @param leaseTime 锁有效时间
         */
        public boolean tryLock(String lockName, long leaseTime) {
    
            RLock rLock = redisson.getLock(lockName);
            boolean getLock = false;
            try {
                getLock = rLock.tryLock( leaseTime, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                log.error("获取Redisson分布式锁[异常],lockName=" + lockName, e);
                e.printStackTrace();
                return false;
            }
            return getLock;
        }
    
        /**
         * 加锁操作(tryLock锁,有等待时间)
         * @param lockName   锁名称
         * @param leaseTime  锁有效时间
         * @param waitTime   等待时间
         */
        public  boolean tryLock(String lockName, long leaseTime,long waitTime) {
    
            RLock rLock = redisson.getLock(lockName);
            boolean getLock = false;
            try {
                getLock = rLock.tryLock( waitTime,leaseTime, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                log.error("获取Redisson分布式锁[异常],lockName=" + lockName, e);
                e.printStackTrace();
                return false;
            }
            return getLock;
        }
    
        /**
         * 解锁
         * @param lockName  锁名称
         */
        public void unlock(String lockName) {
            redisson.getLock(lockName).unlock();
        }
    
        /**
         * 判断该锁是否已经被线程持有
         * @param lockName  锁名称
         */
        public boolean isLock(String lockName) {
            RLock rLock = redisson.getLock(lockName);
            return rLock.isLocked();
        }
    
    
        /**
         * 判断该线程是否持有当前锁
         * @param lockName  锁名称
         */
        public boolean isHeldByCurrentThread(String lockName) {
            RLock rLock = redisson.getLock(lockName);
            return rLock.isHeldByCurrentThread();
        }
    
    
    }
    

    5.注解使用

    /**
     * @author lz
     * @Description: 基于注解的方式 加锁
     * @date 2020/10/24 下午11:01
     */
    @RestController
    @Slf4j
    public class AnnotatinLockController {
    
        @Autowired
        RedissonLock redissonLock;
        /**
         * 模拟这个是商品库存
         */
        public static volatile Integer TOTAL = 10;
    
        @GetMapping("annotatin-lock-decrease-stock")
        @DistributedLock(value="goods", leaseTime=5)
        public String lockDecreaseStock() throws InterruptedException {
            if (TOTAL > 0) {
                TOTAL--;
            }
            log.info("===注解模式=== 减完库存后,当前库存===" + TOTAL);
            return "=================================";
        }
    }
    

     

    展开全文
  • 主要为大家详细介绍了SpringBoot使用Redisson实现分布式锁,秒杀系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 主要介绍了SpringBoot集成Redisson实现分布式锁的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • Redisson实现分布式锁 有关Redisson作为实现分布式锁,总的分3大模块来讲。 1、Redisson实现分布式锁原理 2、Redisson实现分布式锁的源码解析 3、Redisson实现分布式锁的项目代码(可以用于实际项目中) 本文...
  • Redisson实现分布式锁原理
  • 本文将详细介绍redisson实现分布式锁原理。具有很好的参考价值,下面跟着小编一起来看下吧Redisson分布式锁之前的基于注解的锁有一种锁是基本redis的分布式锁,锁的实现我是基于redisson组件提供的RLock,这篇来看看...
  • Redisson实现分布式锁 注解aop的方式加Redisson实现分布式锁(推荐) 一、redis原生方式实现分布式锁 Redis 单线程 SETNX (set if not exists) setnx key value 若key存在则添加失败,若key不存在才会添加存在 ...
  • 最近由于工作很忙,很长时间没有更新博客了,今天为大家带来一篇有关Redisson实现分布式锁的文章,好了,不多说了,直接进入主题。 1. 可重入锁(Reentrant Lock) Redisson的分布式可重入锁RLock Java对象实现了...
  • Redisson实现分布式锁 有关Redisson作为实现分布式锁,总的分3大模块来讲。 1、Redisson实现分布式锁原理 2、Redisson实现分布式锁的源码解析 3、Redisson实现分布式锁的项目代码(可以用于实际项目中) 本文只...
  • 有关Redisson实现分布式锁上一篇博客讲了分布式的锁原理:Redisson实现分布式锁---原理 这篇主要讲RedissonLock和RLock。Redisson分布式锁的实现是基于RLock接口,RedissonLock实现RLock接口。 一、RLock接口 1、...
  • 来源:...1、Redisson实现分布式锁原理 2、Redisson实现分布式锁的源码解析 3、Redisson实现分布式锁的项目代码(可以用于实际项目中)本文只介绍Redisson如何实现分布式锁的原理。一、...
  • Java之——redis并发读写锁,使用Redisson实现分布式锁 转载请注明出处:http://blog.csdn.net/l1028386804/article/details/73523810 1.可重入锁(Reentrant Lock) Redisson的分布式可重入锁RLock Java对象实现了...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,659
精华内容 663
关键字:

redisson实现分布式锁

redis 订阅