Redis分布式锁的三种实现方式_分布式锁解决方案

Redis分布式锁的三种实现方式_分布式锁解决方案总结写在前面:RLockrLock=redissonClient.getLock(“lbhTestLock”);使用tryLock无参方法时,redisson会自动添加一个定时任务,定时刷新锁的失效时间,如果unlock时失败,则会出现该锁一直不释放的情况。而当tryLock传释放时间时,则不会添加这个定时任务。测试如下:1、tryLock无参数@Testp…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

 总结写在前面:

RLock rLock = redissonClient.getLock("lbhTestLock");

使用tryLock无参方法时,redisson会自动添加一个定时任务,定时刷新锁的失效时间,如果unlock时失败,则会出现该锁一直不释放的情况。

而当tryLock传释放时间时,则不会添加这个定时任务。

所以说 ,当服务挂了的时候,不会再有定时任务去刷新锁时间,就会解锁。

测试如下:

1、tryLock 无参数

@Test
public void testLock() throws Exception {
    RLock rLock = redissonClient.getLock("lbhTestLock");
    rLock.tryLock();
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                if (rLock.tryLock()) {
                    System.out.println("成功获取锁");
                } else {
                    System.out.println("未获取锁");
                }
                Thread.sleep(35000);
                if (rLock.tryLock()) {
                    System.out.println("成功获取锁");
                } else {
                    System.out.println("未获取锁");
                }
            } catch (Exception e) {

            }

        }
    }).start();
    Thread.sleep(40000);
    rLock.unlock();
}
输出:

未获取锁

未获取锁

2、tryLock 传释放时间 

rLock.tryLock(0,30,TimeUnit.SECONDS);

输出:

未获取锁
成功获取锁

经测试

@Test
public void testLock2() throws Exception {
    new Thread(new Runnable() {
        @Override
        public void run() {
            RLock rLock = redissonClient.getLock("lbhTestLock");
            rLock.tryLock();
        }
    }).start();
    Thread.sleep(600000);
    //rLock.unlock();
}

redis控制台:

test01:0>ttl lbhTestLock

“29”
test01:0>ttl lbhTestLock

“28”
test01:0>ttl lbhTestLock

“27”
test01:0>ttl lbhTestLock

“25”
test01:0>ttl lbhTestLock

“24”
test01:0>ttl lbhTestLock

“22”
test01:0>ttl lbhTestLock

“21”

//过期时间被重置了
test01:0>ttl lbhTestLock

“28”

// 程序关闭后
test01:0>ttl lbhTestLock

“18”
test01:0>ttl lbhTestLock

“15”
test01:0>ttl lbhTestLock

“13”

如果调用了tryLock()缺没有unlock或者unlock时失败,如果程序不关闭,另外一个线程永远不会获取到这个锁,因为redisson会刷新该锁的过期时间, ttl 会一直重置,除非redisson实例关闭/程序关闭,由redis自动清除

源码分析:

调用tryLock() 无参方法时:

@Override
public boolean tryLock() {
    return get(tryLockAsync());
}
@Override
public RFuture<Boolean> tryLockAsync() {
    return tryLockAsync(Thread.currentThread().getId());
}
public RFuture<Boolean> tryLockAsync(long threadId) {
    return tryAcquireOnceAsync(-1, null, threadId);
}
private RFuture<Boolean> tryAcquireOnceAsync(long leaseTime, TimeUnit unit, final long threadId) {
    //无参时不走这一步
    if (leaseTime != -1) {
        return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
    }
    //将key存于redis数据库中
    RFuture<Boolean> ttlRemainingFuture = tryLockInnerAsync(LOCK_EXPIRATION_INTERVAL_SECONDS, TimeUnit.SECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
    // 添加Listener 重点!
    ttlRemainingFuture.addListener(new FutureListener<Boolean>() {
        //future任务执行完毕后会回调
        @Override
        public void operationComplete(Future<Boolean> future) throws Exception {
            if (!future.isSuccess()) {
                return;
            }
            //获取future任务的返回值 即存储是否成功
            Boolean ttlRemaining = future.getNow();
            // lock acquired
            // 如果存储key成功
            if (ttlRemaining) {
                //添加定时任务 定时刷新锁的过期时间
                scheduleExpirationRenewal(threadId);
            }
        }
    });
    return ttlRemainingFuture;
}
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 " +
                        //设置key过期时间{防止获取锁后线程挂掉导致死锁}
                        "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();
    }
}

添加一个定时任务 每隔过期时间的1/3(默认30s的1/3 10s)刷新锁的过期时间 

Redis分布式锁的三种实现方式_分布式锁解决方案

//lua脚本解析

首先判断这个锁key的map结构中是否存在对应的key8a9649f5-f5b5-48b4-beaa-d0c24855f9ab:anyLock:1,如果存在,就直接调用pexpire命令设置锁key的过期时间,默认30000毫秒。

关于这一原理,可以参考:https://blog.csdn.net/ice24for/article/details/86177152

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/181714.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Ubuntu 20.04 搭建OpenStack Yoga(allinone)

    Ubuntu 20.04 搭建OpenStack Yoga(allinone)文章目录环境准备换源网络配置静态IP桥接永久修改DNS主机名基础服务数据库etcdmemcachedOpenStackNova很多文章都是devstack安装的allinone,我这里使用源码组件手动安装。环境准备这里需要先配置一些环境。首先我这里是虚拟机安装的系统,可能设置的密码不是当前用户的root密码,反正就得重置一下执行下面的命令,然后输入安装系统设置的密码,之后就可以了。sudopasswdroot换源需要先换一个源,方便下载换阿里源gedit/etc/apt/sourc

    2025年7月1日
    5
  • 力争群雄:2012年度IT博客大赛100强脱颖而出[通俗易懂]

    力争群雄:2012年度IT博客大赛100强脱颖而出[通俗易懂]2012年度IT博客大赛于11月20日圆满结束。这一所谓的“海选”阶段为期33天,引无数网友和博主翘首以待,来源包括51CTO、独立个人博客、其他博客服务托管商,以及今年评选新增加的分类如独立博客、学生博客和团队博客等众多博主共同参加了这一角逐,其中100位实力雄厚和人气充盈的博主获得了前100强的殊荣。他们占据了25万张票选中的8成以上份量,并将为2012年度IT博客50…

    2022年7月21日
    12
  • maven打包时打包指定的lib文件夹

    maven打包时打包指定的lib文件夹今天在打包自己的springboot项目时遇到了问题,报找不到类和符号。因为我有些依赖是放在项目lib文件夹中,那么打包的时候要连把它一起打包。修改pom.xml,添加一下内容:<build><plugins><plugin><groupId>or…

    2022年5月29日
    39
  • 表示一个ASCⅡ字符与一个汉字分别要使用几个字节_字,字节

    表示一个ASCⅡ字符与一个汉字分别要使用几个字节_字,字节“一个字等于多少个字节?”是一个不严谨的问法直接回答一个字等于多少个字节,也是不严谨的答法。相关概念:1、位(bit)来自英文bit,音译为“比特”,表示二进制位。位是计算机内部数据储存的最小单位。2、字节(byte)字节来自英文Byte,音译为“拜特”,习惯上用大写的“B”表示。字节是计算机中数据处理的基本单位。3、字(word)计算机进行数据处理时,一次存取、加工和…

    2022年10月1日
    2
  • SpringBoot 配置Mybatis两种方式

    SpringBoot 配置Mybatis两种方式目前SpringBoot被各企业广泛使用,在SpringBoot结合Mybatis使用时,有的人将mapper.xml存放到resouce的目录下,有的想把Mapper.xml和接口放到一起,但是这样/就会存在Mapper.xml加载不了,导致方法无法找到,现在咱们看看两种方式的配置。首先说第一种:也就是网上说的最多的一种,添加依赖,我就不细说了。1.在application.pr…

    2022年6月24日
    33
  • Oracle修改字段类型和长度

    Oracle修改字段类型和长度Oracle 修改字段名 altertable 表名 renamecolumn 旧字段名 to 新字段名

    2025年6月2日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号