【分布式锁】03-使用Redisson实现RedLock原理
前言
前面已经学习了Redission可重入锁以及公平锁的原理,接着看看Redission是如何来实现RedLock的。
RedLock原理
RedLock是基于redis实现的分布式锁,它能够保证以下特性:
- 互斥性:在任何时候,只能有一个客户端能够持有锁;避免死锁:
- 当客户端拿到锁后,即使发生了网络分区或者客户端宕机,也不会发生死锁;(利用key的存活时间)
- 容错性:只要多数节点的redis实例正常运行,就能够对外提供服务,加锁或者释放锁;
RedLock算法思想,意思是不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,n / 2 + 1,必须在大多数redis节点上都成功创建锁,才能算这个整体的RedLock加锁成功,避免说仅仅在一个redis实例上加锁而带来的问题。
这里附上一个前几天对RedLock解析比较透彻的文章:
https://mp.weixin.qq.com/s/gOYWLg3xYt4OhS46woN_Lg
Redisson实现原理
Redisson中有一个MultiLock
的概念,可以将多个锁合并为一个大锁,对一个大锁进行统一的申请加锁以及释放锁
而Redisson中实现RedLock就是基于MultiLock
去做的,接下来就具体看看对应的实现吧
RedLock使用案例
先看下官方的代码使用:
(https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers#84-redlock)
RLock lock1 = redisson1.getLock("lock1");
RLock lock2 = redisson2.getLock("lock2");
RLock lock3 = redisson3.getLock("lock3"); RLock redLock = anyRedisson.getRedLock(lock1, lock2, lock3); // traditional lock method
redLock.lock(); // or acquire lock and automatically unlock it after 10 seconds
redLock.lock(10, TimeUnit.SECONDS); // or wait for lock aquisition up to 100 seconds
// and automatically unlock it after 10 seconds
boolean res = redLock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
redLock.unlock();
}
}
这里是分别对3个redis实例加锁,然后获取一个最后的加锁结果。
RedissonRedLock实现原理
上面示例中使用redLock.lock()或者tryLock()最终都是执行RedissonRedLock
中方法。
RedissonRedLock
继承自RedissonMultiLock
, 实现了其中的一些方法:
public class RedissonRedLock extends RedissonMultiLock {
public RedissonRedLock(RLock... locks) {
super(locks);
} /**
* 锁可以失败的次数,锁的数量-锁成功客户端最小的数量
*/
@Override
protected int failedLocksLimit() {
return locks.size() - minLocksAmount(locks);
} /**
* 锁的数量 / 2 + 1,例如有3个客户端加锁,那么最少需要2个客户端加锁成功
*/
protected int minLocksAmount(final List<RLock> locks) {
return locks.size()/2 + 1;
} /**
* 计算多个客户端一起加锁的超时时间,每个客户端的等待时间
* remainTime默认为4.5s
*/
@Override
protected long calcLockWaitTime(long remainTime) {
return Math.max(remainTime / locks.size(), 1);
} @Override
public void unlock() {
unlockInner(locks);
} }
看到locks.size()/2 + 1
,例如我们有3个客户端实例,那么最少2个实例加锁成功才算分布式锁加锁成功。
接着我们看下lock()
的具体实现
RedissonMultiLock实现原理
```java
public class RedissonMultiLock implements Lock { final List<RLock> locks = new ArrayList<RLock>(); public RedissonMultiLock(RLock... locks) {
if (locks.length == 0) {
throw new IllegalArgumentException("Lock objects are not defined");
}
this.locks.addAll(Arrays.asList(locks));
} public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long newLeaseTime = -1;
if (leaseTime != -1) {
// 如果等待时间设置了,那么将等待时间 * 2
newLeaseTime = unit.toMillis(waitTime)*2;
} // time为当前时间戳
long time = System.currentTimeMillis();
long remainTime = -1;
if (waitTime != -1) {
remainTime = unit.toMillis(waitTime);
}
// 计算锁的等待时间,RedLock中:如果remainTime=-1,那么lockWaitTime为1
long lockWaitTime = calcLockWaitTime(remainTime); // RedLock中failedLocksLimit即为n/2 + 1
int failedLocksLimit = failedLocksLimit();
List<RLock> acquiredLocks = new ArrayList<RLock>(locks.size());
// 循环每个redis客户端,去获取锁
for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
RLock lock = iterator.next();
boolean lockAcquired;
try {
// 调用tryLock方法去获取锁,如果获取锁成功,则lockAcquired=true
if (waitTime == -1 && leaseTime == -1) {
lockAcquired = lock.tryLock();
} else {
long awaitTime = Math.min(lockWaitTime, remainTime);
lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
}
} catch (Exception e) {
lockAcquired = false;
} // 如果获取锁成功,将锁加入到list集合中
if (lockAcquired) {
acquiredLocks.add(lock);
} else {
// 如果获取锁失败,判断失败次数是否等于失败的限制次数
// 比如,3个redis客户端,最多只能失败1次
// 这里locks.size = 3, 3-x=1,说明只要成功了2次就可以直接break掉循环
if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
break;
} // 如果最大失败次数等于0
if (failedLocksLimit == 0) {
// 释放所有的锁,RedLock加锁失败
unlockInner(acquiredLocks);
if (waitTime == -1 && leaseTime == -1) {
return false;
}
failedLocksLimit = failedLocksLimit();
acquiredLocks.clear();
// 重置迭代器 重试再次获取锁
while (iterator.hasPrevious()) {
iterator.previous();
}
} else {
// 失败的限制次数减一
// 比如3个redis实例,最大的限制次数是1,如果遍历第一个redis实例,失败了,那么failedLocksLimit会减成0
// 如果failedLocksLimit就会走上面的if逻辑,释放所有的锁,然后返回false
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思想,遍历所有的Redis客户端,然后依次加锁,最后统计成功的次数来判断是否加锁成功。
申明
本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!
感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫
【分布式锁】03-使用Redisson实现RedLock原理的更多相关文章
- 项目中用到了Redis分布式锁,了解一下背后的原理
前言 以前在学校做小项目的时候,用到Redis,基本也只是用来当作缓存.现在博主在某金融平台实习,发现Redis在生产中并不只是当作缓存这么简单.在我接触到的项目中,Redis起到了一个分布式锁的作用 ...
- Redisson 分布式锁源码 09:RedLock 红锁的故事
前言 RedLock 红锁,是分布式锁中必须要了解的一个概念. 所以本文会先介绍什么是 RedLock,当大家对 RedLock 有一个基本的了解.然后再看 Redisson 中是如何实现 RedLo ...
- Redis分布式锁的正确使用与实现原理
模拟一个电商里面下单减库存的场景. 1.首先在redis里加入商品库存数量. 2.新建一个Spring Boot项目,在pom里面引入相关的依赖. <dependency> <gro ...
- 来吧,展示!Redis的分布式锁及其实现Redisson的全过程
前言 分布式锁是控制分布式系统之间同步访问共享资源的一种方式. 在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要 ...
- 基于redis的分布式锁实现方案--redisson
实例代码地址,请前往:https://gitee.com/GuoqingLee/distributed-seckill redis官方文档地址,请前往:http://www.redis.cn/topi ...
- Springboot分别使用乐观锁和分布式锁(基于redisson)完成高并发防超卖
原文 :https://blog.csdn.net/tianyaleixiaowu/article/details/90036180 乐观锁 乐观锁就是在修改时,带上version版本号.这样如果试图 ...
- Redisson实现Redis分布式锁的底层原理
一.写在前面 现在面试,一般都会聊聊分布式系统这块的东西.通常面试官都会从服务框架(Spring Cloud.Dubbo)聊起,一路聊到分布式事务.分布式锁.ZooKeeper等知识.所以咱们这篇文章 ...
- Redisson 实现分布式锁的原理分析
写在前面 在了解分布式锁具体实现方案之前,我们应该先思考一下使用分布式锁必须要考虑的一些问题. 互斥性:在任意时刻,只能有一个进程持有锁. 防死锁:即使有一个进程在持有锁的期间崩溃而未能主动释放锁, ...
- Redisson 实现分布式锁原理分析
Redisson 实现分布式锁原理分析 写在前面 在了解分布式锁具体实现方案之前,我们应该先思考一下使用分布式锁必须要考虑的一些问题. 互斥性:在任意时刻,只能有一个进程持有锁. 防死锁:即使有 ...
随机推荐
- Tmux 速成教程:技巧和调整
本文转自:http://blog.jobbole.com/87584/ 简介 有些开发者经常要使用终端控制台工作,导致最终打开了过多的标签页.如果你也是他们当中的一员,或者你正在实践结对编程,那么我推 ...
- react 踩坑记
yarn node-sass 安装失败 yarn config set sass-binary-site http://npm.taobao.org/mirrors/node-sass yarn i ...
- Python【map、reduce、filter】内置函数使用说明
题记 介绍下Python 中 map,reduce,和filter 内置函数的方法 一:map map(...) map(function, sequence[, sequence, ...]) -& ...
- 通过zxing生成二维码
二维码现在随处可见,在日常的开发中,也会经常涉及到二维码的生成,特别是开发一些活动或者推广方面的功能时,二维码甚至成为必备功能点.本文介绍通过 google 的 zxing 包生成带 logo 的二维 ...
- 给iPhone找一条平凡之路
新款iPhone马上就要开发布会了,对于全世界的手机从业者来说,现在的感觉就好像中国人过年:早些时候,大家都盼着过年,放鞭炮.享受美食,小孩子还有压岁钱,但随着生活水平的提高,年味越来越淡,昔日的美好 ...
- array-2
数组的基本操作 JS随机操作数组元素 Math.random() 结果为0-1间的一个随机数[0,1) Math.round(num) 参数num为一个数值,函数结果为num四舍五入的整数 Math. ...
- Hexo搭建总结
Hexo搭建过程记录 1.Hexo基本环境搭建 1.Hexo安装前提 Node.js和Git,他们的安装方法可以自行百度. 2.具体安装步骤可以参考: https://www.cnblogs.com/ ...
- Windows 下 LaTeX 手动安装宏包(package)以及生成帮助文档的整套流程
本文简单介绍如何手动安装一个 LaTeX 宏包. 一般来说,下载的 TeX 发行版已经自带了很多宏包,可以满足绝大部分需求,但是偶尔我 们也可能碰到需要使用的宏包碰巧没有安装的情况,这时我们就需要自己 ...
- Linux统计目录下文件个数及代码行数
1. 统计当前目录下,php文件数量 find ./ -name "*.php" | wc -l 2. 统计当前目录下所有php文件代码行数 find ./ -name " ...
- JVM、JRE和JDK三者间的区别和联系
简介:我们利用JDK(调用JAVA API)开发了属于我们自己的JAVA程序后,通过JDK中的编译程序(javac)将我们的文本java文件编译成JAVA字节码,在JRE上运行这些JAVA字节码,JV ...