分布式锁----浅析redis实现
引言
大概两个月前小伙伴问我有没有基于redis实现过分布式锁,之前看redis的时候知道有一个RedLock算法可以实现分布式锁,我接触的分布式项目要么是github上开源学习的,要么是小伙伴们公司项目我们一起讨论问题涉及的,我自己公司的项目中没有实践分布式锁的地方也就没有仔细研究,向小伙伴推荐使用的是redisson实现的就是RedLock算法;当然有能力的还可以自己根据redis作者的RedLock算法描述去实现
插曲
关于RedLock算法的安全性有位大牛 Martin Kleppmann 产生了分歧 How to do distributed locking ;当然Redis作者 antirez 也做出了回应 Is Redlock safe?;当然这是神仙"打架",我们从中学习大牛分析的问题,从而规避即可。
浅析
加锁
redisson通过lua脚本来实现加锁和释放锁,使用lua脚本可以保证原子性
KEYS[1] 就是我们自己定义的 锁名
ARGV[2] 就是生成的锁id UUID+线程id
ARGV[1] 就是生存时间
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 " +
锁对应的value+1 熟悉AQS锁就会知道 这是锁重入
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);
假设 现在线程a,b来请求锁,a先请求到,自定义锁名叫做MY_TEST_LOCK;
b来请求锁时,发现MY_TEST_LOCK 这个锁可以已经存在了,走第二个if;
如果不存在 将key 锁id 超时时间 设置到redis中,返回null表示获取到了锁
第二if判断这个锁名+锁id有没有存在, 如果存在 说明是重入了 就把value加1
返回null 表示获取到了锁
如果不存在返回MY_TEST_LOCK 这个锁的剩余时间,代码中b线程会while循环,
不停的尝试加锁
释放锁
释放锁
KEYS[1] 锁名 例如:MY_TEST_LOCK
KEYS[2] 通道名 当释放锁时发现锁不在redis中时使用
ARGV[1] 锁id
ARGV[2] 锁剩余时间
ARGV[3] 锁重入的值
如果锁不存在 说明已经释放过了 发布redis消息
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end;" +
如果锁对应得value 和redis中value不对应,说明该线程没有持有锁,不能释放
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
锁对应的value -1 也就是释放锁
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
如果锁value还是大于0 说明有重入情况 不删除
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
否则删除 发布redis消息
"else " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;";
例子
这里只是很浅的说一下怎么用,然后解释一下源码里是怎么while循环获取锁的
哨兵模式
Config config = new Config();
config.useSentinelServers().addSentinelAddress(
"redis://172.29.3.245:26378","redis://172.29.3.245:26379", "redis://172.29.3.245:26380")
.setMasterName("mymaster")
.setPassword("a123456").setDatabase(0);
集群模式
Config config = new Config();
config.useClusterServers().addNodeAddress(
"redis://172.29.3.245:6375","redis://172.29.3.245:6376", "redis://172.29.3.245:6377",
"redis://172.29.3.245:6378","redis://172.29.3.245:6379", "redis://172.29.3.245:6380")
.setPassword("a123456").setScanInterval(5000);
单redis模式
Config config = new Config();
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress("redis://127.0.0.1:6380")
.setTimeout(4000 * 10)
.setIdleConnectionTimeout(1000 * 60 * 10);
获取锁
public static final String MY_TEST_LOCK_NAME = "MY_TEST_LOCK";
RedissonClient redissonClient = Redisson.create(config);
RLock lock = redissonClient.getLock(USER_LOCK_NAME);
boolean getLock = false;
try {
getLock = lock.tryLock(10, 5, TimeUnit.SECONDS);
if (getLock){
获取到锁后执行代码
System.out.println(Thread.currentThread().getName()+"线程 锁住");
}
} catch (InterruptedException e) {
//todo 处理异常
e.printStackTrace();
} finally {
lock.unlock();
}
单redis版 获取代码
/**
* waitTime 获取可以等待的时间
* leaseTime 过了这个时间之后 redis这个锁自动消失
*/
@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime);
long current = System.currentTimeMillis();
final long threadId = Thread.currentThread().getId();
先获取一下锁
Long ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
如果获取到了锁 返回值就是null
if (ttl == null) {
return true;
}
time -= (System.currentTimeMillis() - current);
if (time <= 0) {
time <= 0表示超时了
acquireFailed(threadId);
return false;
}
current = System.currentTimeMillis();
final RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
if (!await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
subscribeFuture.addListener(new FutureListener<RedissonLockEntry>() {
@Override
public void operationComplete(Future<RedissonLockEntry> future) throws Exception {
if (subscribeFuture.isSuccess()) {
unsubscribe(subscribeFuture, threadId);
}
}
});
}
acquireFailed(threadId);
return false;
}
try {
time -= (System.currentTimeMillis() - current);
if (time <= 0) {
acquireFailed(threadId);
return false;
}
循环获取锁
while (true) {
long currentTime = System.currentTimeMillis();
ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return true;
}
time -= (System.currentTimeMillis() - currentTime);
if (time <= 0) {
acquireFailed(threadId);
return false;
}
// waiting for message
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));
}
多节点版获取锁 RedissonMultiLock
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
// try {
// return tryLockAsync(waitTime, leaseTime, unit).get();
// } catch (ExecutionException e) {
// throw new IllegalStateException(e);
// }
long newLeaseTime = -1;
if (leaseTime != -1) {
newLeaseTime = unit.toMillis(waitTime)*2;
}
long time = System.currentTimeMillis();
long remainTime = -1;
if (waitTime != -1) {
remainTime = unit.toMillis(waitTime);
}
long lockWaitTime = calcLockWaitTime(remainTime);
需要多少个redis 加锁成功 限制(N/2 + 1)
int failedLocksLimit = failedLocksLimit();
加锁成功集合
List<RLock> acquiredLocks = new ArrayList<RLock>(locks.size());
for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
RLock lock = iterator.next();
boolean lockAcquired;
try {
if (waitTime == -1 && leaseTime == -1) {
lockAcquired = lock.tryLock();
} else {
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 iterator
while (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(http://www.my516.com);
}
}
return true;
}
zookeeper实现分布式锁
在于小伙伴讨论Redis实现分布式锁的同时,我们在万能的github上发现了另一种zookeeper实现分布式锁的方式
zookeeper只是听过,没有用过,这里简单说下区别:
redis 分布式锁,需要自己不断去尝试获取锁,比较消耗性能,但是效率高
zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小,但是健壮性强
另外一点就是,如果是 redis 获取锁的那个客户端 出现 bug 挂了,那么只能等待超时时间之后才能释放锁;而 zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时就自动释放锁
分布式锁----浅析redis实现的更多相关文章
- SpringBoot集成Redis分布式锁以及Redis缓存
https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...
- 解析分布式锁之Redis实现(二)
摘要:在前文中提及了实现分布式锁目前有三种流行方案,分别为基于数据库.Redis.Zookeeper的方案,本文主要阐述基于Redis的分布式锁,分布式架构设计如今在企业中被大量的应用,而在不同的分布 ...
- 分布式锁tair redis zookeeper,安全性
tair分布式锁实现:https://yq.aliyun.com/articles/58928 redis分布式锁:https://www.cnblogs.com/jianwei-dai/p/6137 ...
- 从分布式锁来看redis和zookpeer!
从分布式锁来看redis和zookpeer! 目前网上大部分的基于zookpeer,和redis的分布式锁的文章都不够全面.要么就是特意避开集群的情况,要么就是考虑不全,读者看着还是一脸迷茫.坦白说, ...
- Java分布式:分布式锁之Redis实现
Java分布式:分布式锁之Redis实现 分布式锁系列教程重点分享锁实现原理 Redis锁原理 核心命令 Redis分布式锁的原理是基于其SETNX命令,我们来看SETNX的解释. 实现过程 使用SE ...
- [Java复习] 分布式锁 Zookeeper Redis
一般实现分布式锁都有哪些方式? 使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗? 这两种分布式锁的实现方式哪种效率比较高? 1. Zookeeper 都有哪些使用场 ...
- 分布式锁用Redis还是ZooKeeper?(转载)
文章系网络转载,侵删. 来源:https://zhuanlan.zhihu.com/p/73807097 为什么用分布式锁?在讨论这个问题之前,我们先来看一个业务场景. 图片来自 Pexels 为什么 ...
- 分布式锁(redis/mysql)
单台机器所能承载的量是有限的,用户的量级上万,基本上服务都会做分布式集群部署.很多时候,会遇到对同一资源的方法.这时候就需要锁,如果是单机版的,可以利用java等语言自带的并发同步处理.如果是多台机器 ...
- 【分布式锁】Redis实现可重入的分布式锁
一.前言 之前写的一篇文章<细说分布式锁>介绍了分布式锁的三种实现方式,但是Redis实现分布式锁关于Lua脚本实现.自定义分布式锁注解以及需要注意的问题都没描述.本文就是详细说明如何利用 ...
随机推荐
- Flutter实战视频-移动电商-18.首页_火爆专区后台接口调试
18.首页_火爆专区后台接口调试 楼层结束之后有个火爆专区.到地图有个上拉加载的效果 lib/config/service_url.dart 首先找到我们的接口配置文件,增加接口的配置 lib/ser ...
- 模板 - 洲阁筛 + min25筛
好像在某些情况下杜教筛会遇到瓶颈,先看着.暑假学一些和队友交错的知识的同时开这个大坑.
- HDU - 1715 - 大菲波数 - JAVA
http://acm.hdu.edu.cn/showproblem.php?pid=1715 import java.io.*; import java.util.*; import java.mat ...
- git clone 之后 , 如何复制文件到文件夹 并 上传
1. 关于 _netrc machine github.com login myid password mypassword machine bitbucket.org login myid pass ...
- C. Chessboard( Educational Codeforces Round 41 (Rated for Div. 2))
//暴力 #include <iostream> #include <algorithm> #include <string> using namespace st ...
- [题解](数学)BZOJ_1257_余数求和
来源:https://blog.csdn.net/loi_dqs/article/details/50522975 并不知道为什么是sqrt(n)的段数......书上写的看不懂...... 但是这个 ...
- Tinghua Data Mining 3
特征选择 男女身高 男女抽烟 先验分布 熵 衡量系统的不确定性 属性的价值 降低了不确定性 降低的幅度越高越好 主成分分析 旋转是的数据间的correlation消失掉 Q是正交阵 七长八短,长宽相关 ...
- 【bzoj1726】Roadblocks
1726: [Usaco2006 Nov]Roadblocks第二短路 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 1578 Solved: 795[ ...
- POJ-3159-Candies-(差分约束,Dijkstra)
链接:https://vjudge.net/problem/POJ-3159 题意: N个小孩,M个约束 以A,B,C给出.即小孩B的糖果不能比A多C以上. 思路: 差分约束: 若有 A-B < ...
- GYM 101889J(枚举、环上gcd)
答案只有n - 1种暴举即可,对于每种,gcd是一那踩雷稳了,否则看雷的分布有没有把模余占满. const int maxn = 1e5 + 5; int n, ans; char str[maxn] ...