Redisson实现分布式锁原理、自动续期机制、应用场景

前言:
在学习分布式锁之前,应该首先要分析一下使用分布式锁的必须要考虑的一些问题
1、互斥性:在并发的任意时刻,只能有一个线程拿到锁

2、防锁死:即使有一个线程在持有锁的期间崩溃而未能主动释放锁,还要有其他方式去释放锁从而保证其他进程能获取到锁

3、加锁和解锁的必须是同一个线程,解铃还须系铃人

4、锁续期:执行业务耗时的时间超过了锁的等待时间,必须进行锁续期

一、Redisson实现分布式锁

1、加锁原理
【1】Redisson 和 Redis 实现分布式锁是一致的,是不过 Redisson 是封装了 lua 脚本,保证获取、判断、加锁操作的原子性
【2】看一下加锁执行的 lua 脚本

if (redis.call('exists', KEYS[1]) == 0) then " +
   "redis.call('hincrby', 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]);"

【3】一个客户端要加锁,它首先会根据hash节点选择一台机器,这里注意仅仅只是选择一台机器,紧接着就会发送一段封装好的 lua 脚本到 redis 上,比如加锁的那个锁key就是"mylock",并且设置的时间是30秒,30秒后mylock锁就会被自动释放

2、锁互斥机制
【1】第一个线程加锁完成后,此时,如果客户端 2 来尝试加锁会如何呢?首先,第一个 if 判断会执行 exists myLock,发现 myLock 这个锁 key 已经存在了。接着第二个 if 判断,判断一下,myLock 锁 key 的 hash 数据结构中,是否包含客户端 2 的 ID,这里明显不包含客户端 2 的ID,因为那里包含的是客户端 1 的 ID。所以,客户端 2 会执行:

return redis.call('pttl', KEYS[1]);

返回的是一个数字,这个数字就是 myLock 这个锁 key 的剩余生存时间

3、锁阻塞机制
【1】当锁正在被其他线程占用的时候,等待获取锁的线程并不是通过一个 while(true) 死循环去获取锁,而是利用了 Redis 的发布订阅机制,通过 await 方法阻塞等待锁的进程,有效的解决 不断的申请锁浪费资源的问题

4、锁续期机制
【1】客户端 1 加锁的默认生存(等待)时间为30秒,如果超过了30秒,客户端 1 还想一直持有这把锁,怎么办?
【2】Redisson 提供了一个续期机制,只要客户端 1 一旦加锁成功,就会启动一个 Watch Dog (看门狗)
【3】Watch Dog 它是一个后台定时任务线程,会每隔10秒(internalLockLeaseTime / 3)检查一下(遍历 EXPIRATION_RENEWAL_MAP 里面线程 id 然后根据线程 id 去 Redis 中查,如果存在就会延长 key 的时间),如果客户端 1 还持有锁key,那么就会不断的延长锁key的生存时间,直至业务执行结束或者该线程主动释放锁

注意:

1、参数 leaseTime 必须是 -1 才会开启 Watch Dog 机制,也就是如果你想开启 Watch Dog 机制必须使用默认的加锁时间为 30s。如果你自定义释放时间,超过这个时间锁就会自定释放,并不会延长

2、这里有个问题,如果服务宕机了,Watch Dog 机制后台定时任务线程也就没有了,此时就不会延长 key 的过期时间,到了 30s 之后就会自动过期,其他线程就可以获取到锁

【4】续期说明、用法示例:

//拿锁失败会不停重试
//具有 Watch Dog 自动延期机制,默认续30s 每隔30/3=10 秒续到30s
lock.lock();

//businessLock.tryTime() 秒之后停止重试加锁,返回false
//具有 Watch Dog 自动延期机制,默认续30s 每隔30/3=10 秒续到30s
boolean locked1 = lock.tryLock(businessLock.tryTime(), businessLock.timeUnit());

//businessLock.tryTime() 秒之后停止重试加锁,返回false
//不具有 Watch Dog 自动延期机制
boolean locked2 = lock.tryLock(businessLock.tryTime(), businessLock.expire(), businessLock.timeUnit());

//businessLock.tryTime() 秒之后停止重试加锁,返回false
//只有 leaseTime(默认-1) 等于 -1 时,才具有 Watch Dog 自动延期机制,默认续30s 每隔30/3=10 秒续到30s
boolean locked3 = lock.tryLock(businessLock.tryTime(), -1, businessLock.timeUnit());

注意:
【1】为保证业务执行耗时内锁不过期,最好使用默认的 30s 过期时间、或者自定义更长的过期时间
【2】拿到锁之后的等待时间,最好大于过期时间,保证执行业务耗时完成之后让其自己释放锁,否则业务还没执行完毕后续线程就尝试拿锁、会拿锁失败。(只有请求参数不一致的情况才可以测出此现象)

5、锁可重入机制 
【1】可重入锁就是在拿到锁之后、在内部还可以再次拿到锁,示例代码如下:

@Override
public void lock() {
    RLock lock = redissonSingle.getLock("myLock");
    try {
        lock.lock();
 
        // 执行业务
        doBusiness();
 
        lock.lock();
 
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 释放锁
        lock.unlock();
        lock.unlock();
        logger.info("任务执行完毕, 释放锁!");
    }
}

【2】如果加锁支持可重入锁,那么解锁呢?执行解锁就Ok了,拿几次锁解锁几次

lock.unlock()

二、分布式锁常用场景

1、并发请求下,保证数据的幂等性,让请求排队一个一个来

2、定时任务在集群情况下防止任务重复执行,可以使用分布式锁解决,只让拿锁成功的那个线程执行,其他拿锁失败的线程直接返回或者抛异常、不需要执行。(这里注意拿锁后等待时间必须为 0 让线程来竞争锁,否则等拿到锁的那个线程执行完毕后锁释放了,后续线程肯定能拿到锁,这样依然还会多台机器重复执行任务)

三、总结

1、Redisson 通过 Watch Dog 机制很好的解决了锁的续期问题

2、Redisson 基于 Redis 性能很高,适合对性能要求高的场景

3、Redisson 实现分布式可重入锁,比原生的 SET mylock userId NX PX milliseconds + lua 实现的效果更好些,虽然基本原理都一样,但是它帮我们封装了内部的执行细节(官方封装更严谨)

4、在等待申请锁资源占用上也做了一些优化,减少了无效的锁申请,提升了资源的利用率

5、Redisson 的获取锁默认是非公平的(随机抢锁),可以使用 getFairLock() 获取公平锁对象(线程将以其请求的时间顺序获取锁),如下:

//公平锁,保证 Redisson 客户端线程将以其请求的顺序获得锁
RLock fairLock = redissonClient.getFairLock("fairLock");

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
概要介绍: 本课程主要是介绍并实战一款java中间件~redisson,介绍redisson相关的核心技术栈及其典型的应用场景,其中的应用场景就包括布隆过滤器、限流器、短信发送、实时/定时邮件发送、数据字典、分布式服务调度等等,在业界号称是在java项目里正确使用redis的姿势。本课程的目标就在于带领各位小伙伴一起学习、攻克redisson,更好地巩固自己的核心竞争力,而至于跳槽涨薪,自然不在话下!  课程内容: 说起redisson,可能大伙儿不是很熟悉,但如果说起redis,想必肯定很多人都晓得。没错,这家伙字如其名,它就是架设在redis基础上的一款综合性的、新型的中间件,号称是java企业级应用开发中正确使用redis的姿势/客户端实例。 它是架设在redis基础之上,但拥有的功能却远远多于原生Redis 所提供的,比如分布式对象、分布式集合体系、分布式锁以及分布式服务调度等一系列具有分布式特性的对象实例… 而这些东西debug将在本门课程进行淋漓尽致的介绍并实战,除此之外,我们将基于spring boot2.0搭建的多模块项目实战典型的应用场景:对象存储、数据字典、短信发送、实时/定时邮件发送、布隆过滤器、限流组件、分布式服务调度....课程大纲如下所示: 下面罗列一下比较典型的核心技术栈及其实际业务场景的实战,如下图所示为redisson基于订阅-发布模式的核心技术~主题Topic的实际业务场景,即实时发送邮件: 而下图则是基于“多值映射MultiMap”数据结构实战实现的关于“数据字典”的缓存管理: 除此之外,我们还讲解了可以与分布式服务调度中间件dubbo相媲美的功能:分布式远程服务调度,在课程中我们动手搭建了两个项目,用于分别充当“生产者”与“消费者”角色,最终通过redisson的“服务调度组件”实现服务与服务之间、接口与接口之间的调用!  课程收益: (1)认识并掌握redisson为何物、常见的几种典型数据结构-分布式对象、集合、服务的应用及其典型应用场景的实战; (2)掌握如何基于spring boot2.0整合redisson搭建企业级多模块项目,并以此为奠基,实战企业级应用系统中常见的业务场景,巩固相应的技术栈! (3)站在项目管理与技术精进的角度,掌握对于给定的功能模块进行业务流程图的绘制、分析、模块划分、代码实战与性能测试和改进,提高编码能力与其他软实力; (4)对于Java微服务、分布式、springboot精进者而言,学完本课程,不仅可以巩固提高中间件的实战能力,其典型的应用场景更有助于面试、助力相关知识点的扫盲! 如下图所示: 关键字:Spring Boot,Redis,缓存穿透,缓存击穿,缓存雪崩,红包系统,Mybatis,高并发,多线程并发编程,发送邮件,列表List,集合Set,排行榜,有序集合SortedSet,哈希Hash ,进阶实战,面试,微服务、分布式 适用人群:redisson学习者,分布式中间件实战者,微服务学习者,java学习者,spring boot进阶实战者,redis进阶实战者

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值