redis分布式锁在项目中的实现二 Redisson的使用,原理及源码解读

Redisson

本文章对应的代码地址:https://github.com/zhangshilin9527/redisson-study

背景

接着上一篇文章《redis分布式锁在项目中的实现一 手动实现redis分布式锁》我们继续说一下redis分布式锁,如果还想继续优化一下上面的Demo3,就需要我们引入一个新的组件Redisson

 

什么是Redisson?

Redisson是最先进,最简单的Redis Java客户端,是基于Redis的对象,集合,锁,同步器和Java上分布式应用程序所需的服务。

快速开始

1.引入maven

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

2.增加配置

    @Bean
    public RedissonClient redisson() {
        Config config = new Config();
        //redis集群方式
        config.useClusterServers().setScanInterval(2000)
                .addNodeAddress("redis://127.0.0.1:7000").addNodeAddress("redis://127.0.0.1:7001").setPassword("abcdef");
        return Redisson.create(config);
    }

3.代码示例

    public String redisLockDemo4() {
        String redisKey = "redis_key_004";
        RLock redissonLock = redissonClient.getLock(redisKey);
        try {
            redissonLock.lock();
            //获取到redis锁,进行业务逻辑
            System.out.println("获取到redis锁");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redissonLock.unlock();
        }
        return "done";
    }

redisson锁还支持读写锁,红锁等,具体其他使用方法请参照官方地址:https://redisson.org/

Redisson分布式锁原理

我使用一张图来解释一下Redisson怎么获取锁及防止锁失效的。

1.线程1,线程2,线程3同时去获取锁;

2.线程1获取到锁,线程2,线程3未获取到锁;

3.线程2,线程3while循环,进行自旋获取锁

4.线程1后台开启线程,每1/3超时时间执行一次锁延迟动作,防止锁失效。

 

Redisson源码解析

redisson源码使用了大量的lua脚本,使用lua脚本操作redis主要由以下几个有点:

1.减少网络开销;

2.原子操作;

3.保证事务;

不过不用担心,redisson中的lua脚本命令基本上可以看明白,看不明白的通过百度也基本可以确定,下面我们来看一下Redisson是如何加锁的。

我们Redisson是如何获取锁的,主要是通过下面这个方法

org.redisson.RedissonLock#lockInterruptibly(long, java.util.concurrent.TimeUnit)
    org.redisson.RedissonLock#tryAcquireAsync
        org.redisson.RedissonLock#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; " +
                  "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));
    }

整理一下这个流程:

1.判断是否是当前key是否存在

2.执行hset命令 key 是我们传进来的redisKey,arvg是当前线程,vaule是1 

3.给锁设置时间

4.返回nil

下面的是支持重入锁,可以先不看,这样我们就获取到锁了,但是他是怎么给锁延长超时时间的呢,请看下面代码

当获取到锁之后,就会执行org.redisson.RedissonLock#scheduleExpirationRenewal方法,我们来看一下这个代码

    private void scheduleExpirationRenewal(final long threadId) {
        if (expirationRenewalMap.containsKey(getEntryName())) {
            return;
        }

        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                
                RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), 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.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
                
                future.addListener(new FutureListener<Boolean>() {
                    @Override
                    public void operationComplete(Future<Boolean> future) throws Exception {
                        expirationRenewalMap.remove(getEntryName());
                        if (!future.isSuccess()) {
                            log.error("Can't update lock " + getName() + " expiration", future.cause());
                            return;
                        }
                        
                        if (future.getNow()) {
                            // reschedule itself
                            scheduleExpirationRenewal(threadId);
                        }
                    }
                });
            }
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

        if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {
            task.cancel();
        }
    }

看一下lua脚本这段,其实很简单,就几行:

1.判断当前可以是否存在

2.存在的话重新设置超时时间

3.不存在则返回0

但是需要注意的是它执行周期是超时时间的1/3,比如设置30s超时,那么每隔10s他都会执行一次锁延时任务。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值