redLock也是Redis提供的一个分布式锁,和redissonLock有些区别

是什么

RedLock可以指定等待时间,也就是说,假如我指定了等待时间waitTime是2S,比如:
1.A线程来加锁,正常去执行业务逻辑
2.B线程也来加锁,此时会加锁失败,那B线程最多等待2S,如果超过了2S还没有获取到分布式锁,那B线程加锁就返回false,表示加锁失败

redLock的思想是:

  1. redLock的使用,需要有奇数台独立部署的Redis节点
  2. 在加锁的时候,会分别去N台节点上加锁,如果半数以上的节点加锁成功,就认为当前线程加锁成功

还有一个点:不管是redLock,还是redissonLock,两者底层都是通过相同的lua脚本来加锁、释放锁的,所以,两者只是外部形态的不同,底层是一样的

redLock是继承了redissonMultiLock,大部分的逻辑,都是在redissonMultiLock中去实现的,所以源码部分,大部分都是RedissonMultiLock

加锁源码

org.redisson.RedissonMultiLock#tryLock(long, long, java.util.concurrent.TimeUnit)
在加锁的时候,会调用到这个方法,这里入参的意思分别是:
第一个waitTime是锁等待时间
leaseTime是锁加锁时间
TimeUnit是时间单位

我的理解是:线程A在加锁的时候,会使用leaseTime作为锁失效时间,如果没有指定,就是默认30S,然后进行锁续期;如果指定了,那就使用指定的阈值,并且不会自动化续期
线程B来加锁的时候,如果线程A已经对同一个key进行了加锁,那线程B最多等待waitTime时间,如果到了这个时间,线程B依旧没有获取到锁,那线程B加锁的方法就会返回false,表示加锁失败

一、最外层逻辑判断

public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {long newLeaseTime = -1;// 1.这里是在指定了加锁时间,会将新的加锁时间设置为锁等待时间的2倍;这里我没怎么理解为什么要这么做,这里也是之前踩过的一个坑if (leaseTime != -1) {newLeaseTime = unit.toMillis(waitTime)*2;}long time = System.currentTimeMillis();long remainTime = -1;// 2.如果waitTime不等于-1,就是指定了等待时间的场景下,将等待时间转换为毫秒if (waitTime != -1) {remainTime = unit.toMillis(waitTime);}// 3.这里的计算,会用remainTime / Redis机器的数量;所以:如果我指定的等待时间是2000ms,那这里获取到的lockWaitTime就是666mslong lockWaitTime = calcLockWaitTime(remainTime);int failedLocksLimit = failedLocksLimit();List<RLock> acquiredLocks = new ArrayList<RLock>(locks.size());// 4.对所有的Redis节点进行遍历,然后依次去加锁,如果加锁失败的话,会进行一些列的处理;这里加锁失败的处理逻辑我还没有细看,后面再补充for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {RLock lock = iterator.next();boolean lockAcquired;try {// 4.1 如果等待时间未指定、锁超时时间未指定,那就调用下面这个方法即可,这个方法就和redissonLock加锁是一样的if (waitTime == -1 && leaseTime == -1) {lockAcquired = lock.tryLock();} else {// 4.2 反之,就会调用入参了waitTime和leaseTime的方法long awaitTime = Math.min(lockWaitTime, remainTime);lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);}} catch (RedisResponseTimeoutException e) {unlockInner(Arrays.asList(lock));lockAcquired = false;} catch (Exception e) {lockAcquired = false;}if (lockAcquired) {acquiredLocks.add(lock);} else {if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {break;}if (failedLocksLimit == 0) {unlockInner(acquiredLocks);if (waitTime == -1 && leaseTime == -1) {return false;}failedLocksLimit = failedLocksLimit();acquiredLocks.clear();// reset iteratorwhile (iterator.hasPrevious()) {iterator.previous();}} else {failedLocksLimit--;}}if (remainTime != -1) {remainTime -= (System.currentTimeMillis() - time);time = System.currentTimeMillis();if (remainTime <= 0) {unlockInner(acquiredLocks);return false;}}}if (leaseTime != -1) {List<RFuture<Boolean>> futures = new ArrayList<RFuture<Boolean>>(acquiredLocks.size());for (RLock rLock : acquiredLocks) {RFuture<Boolean> future = rLock.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);futures.add(future);}for (RFuture<Boolean> rFuture : futures) {rFuture.syncUninterruptibly();}}return true;
}

上面这段代码,是redLock加锁的外层逻辑,有几个点需要注意:

  1. 如果同时指定了waitTime和leaseTime,那leaseTime就会变为waitTime * 2, 这个点一定要注意
  2. 如果没有指定waitTime和leaseTime,底层加锁,就和redissonLock加锁的逻辑是一样的
  3. 如果指定了waitTime和leaseTime,会有一段逻辑的处理

二、针对waitTime和leaseTime的逻辑
这个是上面4.2处注解对应的方法

// 这是加锁的核心逻辑,这里我们不管前面传过来的加锁时间等待时间是多少,我们就认为等待时间是2S,加锁时间是5S
@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {// 1.这里的这个time就是当前加锁的线程如果加锁失败,最多等待的时间;获取到当前时间、当前线程IDlong time = unit.toMillis(waitTime);long current = System.currentTimeMillis();final long threadId = Thread.currentThread().getId();// 2.这里就是尝试加锁的逻辑,和redissonLock加锁调用的是同一个方法,就不做过多注释;如果返回的ttl不为null,就表示加锁失败Long ttl = tryAcquire(leaseTime, unit, threadId);// lock acquiredif (ttl == null) {return true;}// 3.在第一次加锁失败之后,会判断等待时间是否超过了阈值;如果超过了,就是说等待时间超过了2S,就返回falsetime -= (System.currentTimeMillis() - current);if (time <= 0) {acquireFailed(threadId);return false;}// 4.如果不超过阈值,就订阅对应的channelcurrent = System.currentTimeMillis();final RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);// 4.1 这里是调用了await方法,跳进去,发现调用的是countDownLatch.await()方法,总之如果await失败,会进行一系列的处理if (!await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {if (!subscribeFuture.cancel(false)) {subscribeFuture.addListener(new FutureListener<RedissonLockEntry>() {@Overridepublic void operationComplete(Future<RedissonLockEntry> future) throws Exception {if (subscribeFuture.isSuccess()) {unsubscribe(subscribeFuture, threadId);}}});}acquireFailed(threadId);return false;}try {// 4.2 在await了之后,再次判断是否超过了等待时间time -= (System.currentTimeMillis() - current);if (time <= 0) {acquireFailed(threadId);return false;}// 5.如果依旧没有达到阈值,就进行尝试加锁,如果加锁成功,则返回true// 如果加锁失败,在time的基础之上,再减去加锁的耗时while (true) {long currentTime = System.currentTimeMillis();ttl = tryAcquire(leaseTime, unit, threadId);// lock acquiredif (ttl == null) {return true;}time -= (System.currentTimeMillis() - currentTime);if (time <= 0) {acquireFailed(threadId);return false;}// waiting for message// 6.代码执行到这里,表示依旧没有超过等待的阈值;这里的getLatch()获取到的是semaphore的一个实现类,会调用其UNSAFE.park(false, nanos);方法currentTime = System.currentTimeMillis();if (ttl >= 0 && ttl < time) {getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} else {getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);}time -= (System.currentTimeMillis() - currentTime);if (time <= 0) {acquireFailed(threadId);return false;}}} finally {unsubscribe(subscribeFuture, threadId);}
//        return get(tryLockAsync(waitTime, leaseTime, unit));
}

根据这段代码的注释,就可以看出来,在加锁的时候,多了一个waitTime的判断,也就是说,如果线程B等待的时候,超过了waitTime,就直接return false, 加锁失败,而不是像redissonLock那样,不停的休眠、重试加锁

总结

释放锁的逻辑和redissonLock释放锁的逻辑几乎上是一样的,只是redLock外层多了一个for循环而已,循环所有的Redis节点,分别去每个节点释放锁

总结下,redLock和redissonLock两者的区别

  1. 前者可以指定waitTime等待时间,线程等待时间超过了阈值,就返回false,表示加锁失败;后者不可以指定waitTime,如果线程加锁失败,就while(true),休眠、重试加锁
  2. 前者要求Redis节点独立部署,分别去多个独立节点上进行加锁,半数以上节点加锁成功,就认为成功;后者没有这个要求,集群部署即可,master会把加锁的key同步到slave节点
  3. 前者指定了waitTime和leaseTime之后,leaseTime会变为waitTime的2倍,这是比较容易踩坑的一个点
  4. 两者最底层,都是通过相同的lua脚本去加锁、解锁的

Redis分布式锁之:RedLock相关推荐

  1. 得物技术浅谈深入浅出的Redis分布式锁

    一.什么是分布式锁 1.1 分布式锁介绍 分布式锁是控制不同系统之间访问共享资源的一种锁实现,如果不同的系统或同一个系统的不同主机之间共享了某个资源时,往往需要互斥来防止彼此干扰来保证一致性. 1.2 ...

  2. 基于Redis的分布式锁和Redlock算法

    来自:后端技术指南针 1 前言 今天开始来和大家一起学习一下Redis实际应用篇,会写几个Redis的常见应用. 在我看来Redis最为典型的应用就是作为分布式缓存系统,其他的一些应用本质上并不是杀手 ...

  3. Redlock:Redis分布式锁的实现

    来源:阿飞的博客 普通实现 说道Redis分布式锁大部分人都会想到:setnx+lua,或者知道set key value px milliseconds nx.后一种方式的核心实现命令如下: - 获 ...

  4. Redis分布式锁(Redlock官方文档的理解)

    Redis分布式锁(Redlock官方文档的理解) 我github博客原文 官网解释 分布式锁在许多不同进程下需要对共享资源进行互斥操作的环境下,十分需要 Redis作者提出了 Redlock 算法 ...

  5. Redlock:Redis分布式锁最牛逼的实现

    普通实现 说道Redis分布式锁大部分人都会想到:setnx+lua,或者知道set key value px milliseconds nx.后一种方式的核心实现命令如下: - 获取锁(unique ...

  6. redis分布式锁 在集群模式下如何实现_收藏慢慢看系列:简洁实用的Redis分布式锁用法...

    在微服务中很多情况下需要使用到分布式锁功能,而目前比较常见的方案是通过Redis来实现分布式锁,网上关于分布式锁的实现方式有很多,早期主要是基于Redisson等客户端,但在Spring Boot2. ...

  7. 快来学习Redis 分布式锁的背后原理

    以前在学校做小项目的时候,用到Redis,基本也只是用来当作缓存.可阿粉在工作中发现,Redis在生产中并不只是当作缓存这么简单.在阿粉接触到的项目中,Redis起到了一个分布式锁的作用,具体情况是这 ...

  8. Redis分布式锁使用不当,酿成一个重大事故,超卖了100瓶飞天茅台!!!

    点击关注公众号,Java干货及时送达 来源:juejin.cn/post/6854573212831842311 基于Redis使用分布式锁在当今已经不是什么新鲜事了. 本篇文章主要是基于我们实际项目 ...

  9. Redis 分布式锁使用不当,酿成一个重大事故,超卖了100瓶飞天茅台!!!

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 基于Redis使用分布式锁在当今已经不是什么新鲜事了. 本 ...

  10. 秒杀商品超卖事故:Redis分布式锁请慎用!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:浪漫先生 来源:juejin.im/post/6854573 ...

最新文章

  1. 查看Linux中硬链的所有文件路径
  2. Activiti工作流实战-2
  3. cn.hutool.poi.excel.ExcelUtil 只输出指定的标题
  4. jQuery Mobile页面返回无需重新get
  5. Fedora 17 install VMWare tool
  6. PHP大数据处理【转】
  7. 网页视频15分钟自动暂停_在15分钟内学习网页爬取
  8. python库tkinter、pygame中几点需要注意的问题
  9. 如何实现Activiti的分支条件的自定义配置(转)
  10. 从头开始-02.C语言基础
  11. 阿里云服务器windows系统上Nodejs监听80端口报错!
  12. brave mysql_MYSQL常用命令
  13. HTML页面楷体gb2312字体,楷体gb2312
  14. python爬取千图网_python 爬取 花瓣网图片,千图网图片
  15. 朱晔的互联网架构实践心得S2E1:业务代码究竟难不难写?
  16. 适合CCCV操作的Fabric 1.4.6部署方案
  17. 如何更改IE窗口初始大小及位置
  18. 埃克森尔科技参与IEEE BDL SC数字身份标准工作组会议
  19. f1签证计算机专业容易拒签吗,美国签证F1被拒,拒签调档出来原来是这个原因...
  20. 【第3篇】人工智能(AI)语音测试原理和实践

热门文章

  1. 获取最顶层的ViewController top ViewController swift
  2. 如何在Swift中掌握协议
  3. linux内核 header.s,arm架构的linux内核中,clrex指令的作用是什么
  4. 会话推荐和 序列推荐
  5. 多数元素 在数组中出险次数大于n/2
  6. php5.6软件下载,【PHP下载】PHP for Linux 5.6.6-ZOL软件下载
  7. php重写mysql类_如何成功重写旧的mysql-php代码与已弃用的mysql_ *函数?
  8. 数据分析中会常犯哪些错误,如何解决? 二
  9. linux常见命令用法之(一)
  10. 【BZOJ 3531 Sdoi2014】旅行【动态开点线段树+树链剖分】