1.redis锁前世即基于单Redis节点的分布式锁,诸如setkey value px milliseconds nx

前世者,必将经历种种磨砺,才能稍微符合一些主流。推荐自测非常好用的redis工具(http://try.redis.io/)

第一劫:
dos命令版本
1)setnx job "hello"
如果当前job存在,则返回0表明赋值不成功。
如果当前job不存在,则返回1,表明赋值成功。
2)del job
单独删除操作命令
Java代码版本
1)redisCacheTemplate.opsForValue().set("key","123");(springboot+Redis)
如果当前key存在则覆盖,不存在则继续添加
2)redisCacheTemplate.delete("key");
删除存在的key操作
劫语:无论是dos命令操作,还是通过Java代码实现都不难发现,一个线程占用当前资源时候,如果请求执行因为某些原因意外推出了,导致独占的锁一直没有释 放,那么这个锁将一直存在。以至于以后缓存得不到任何的更新。

第二劫:
劫语应对:既然占有的锁一直释放不了,我们可以通过锁添加失效时间
Java命令版本
1)redisCacheTemplate.opsForValue().setIfAbsent("key",UUID.randomUUID());
(redis封装的函数setIfAbsent(),追踪至底层代码,实际就是connection.setNX(rawKey, rawValue)一个原子性操作)
如果当前key存在,则赋值不成功,如果不存在的话,则赋值成功
2)redisCacheTemplate.expire("key",60,TimeUnit.SECONDS);
并且给当前锁设置失效时间.设置超时时间需要合理评估,过长或者过短都是问题。
劫语:有效的锁定了key,并且设置了失效时间,但是setnx方法只能由一个线程占有,如果其中执行逻辑比较缓慢,缓慢到超过设置的失效时间,另外一个线程获 取key,执行到中间执行逻辑代码,出现冲突。

第三劫:
劫语应对:既然出现了由于中间逻辑执行缓慢情况,可以通过LUA脚本来加长当前key失效时间。
LUA脚本
1)伪代码
if redis.call("get",KEYS[1]) == ARGV[1] then
redis.call("set",KEYS[1],ex=3000)
else
getDLock();
如果获取当前锁还没失效,则增加当前锁失效时间,如果已经失效,则重新获取锁
劫语:就目前情况依旧不能解决两个线程同时操作独占资源情况。

第四劫:(普遍单机操作应用方法setkey value px milliseconds)
劫语应对:既然占有锁和锁添加超时时间,会存在一个执行,一个没有执行情况。我们就把他们封装城一个事务操作处理。
dos命令版本
1)setex mykey 60 redis
如果当前mykey没有值,则赋值redis,并且声明超时时间为60s(单位为second)
2)setex mykey 60 java(报错)
如果当前mykey存在值,并且没有超过超时时间,则赋值失败。
3)setex mykey 60 cainiao(返回1)
如果当前mykey存在值,并且超时时间已过,则赋值成功。
java命令版本
目前redis操作jar包中,已经对获取key值,还有设置失效方法封装成一个操作。
1)redisCacheTemplate.opsForValue().set("key", UUID.randomUUID(),60,TimeUnit.SECONDS);
追踪其底层代码,你就会发现,其实就是原子性操作
connection.setEx(rawKey, TimeoutUtils.toSeconds(timeout, unit), rawValue);
劫语:当单机REDIS服务停用,当前分布式锁方案仍旧存在问题。

第五劫:(普遍单机操作应用方法setNX+LUA(释放锁))
java命令版本
1)setnx方式获取锁,并且设置超时时间
public static boolean lock(String key,String uuid,int expire){
if(null == key){
return false;
}
try {
Jedis jedis = getJedisPool().getResource();
String res = jedis.set(key,uuid,"NX","EX",expire);
jedis.close();
return res!=null && res.equals("OK");
} catch (Exception e) {
return false;
}
}
需要注意事项:uuid值,需要唯一标识。否则会导致 “信号错误”,释放了不该释放的锁
A----->获取锁,占用资源 B-------->尝试获取失败,继续尝试
A----->执行公共资源(未执行完),锁失效 B-------->尝试获取锁成功,执行公共资源
A----->执行完成,释放锁(A B锁一起释放)B-------->B还没操作成功

2)LUA释放锁结构,需要判断当前锁,是否为需要释放的锁,这就是为何声明锁唯一的原因
static String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
public static boolean releaseLock(String key ,String lockValue){
if(key == null || lockValue == null) {
return false;
}
try {
Jedis jedis = getJedisPool().getResource();
Object res =jedis.eval(luaScript,Collections.singletonList(key),Collections.singletonList(lockValue));
jedis.close();
return res!=null && res.equals(lockReleaseOK);
} catch (Exception e) {
return false;
}
}
劫语:当单机REDIS服务停用,当前分布式锁方案仍旧存在问题。

2.redis锁今生,基于redlock封装而成的redission框架

对于前世redis分布式锁各种解决方案,无论是成熟单机方案 1.SETNX+LUA 2.SET unique_value nx px milliseconds ,都局限性很大
所以Martin发布了一种算法redlock来进行集群(完全互相独立,不存在主从复制或者其他集群协调机制)操作分布式锁
算法如下:
1.获取当前Unix时间,以毫秒为单位。
2.依次尝试从5个实例,使用相同的key和具有唯一性的value(例如UUID)获取锁。并且设置一个超时时间(一般是5-50毫秒,远小于失效时间)
3.客户端当前时间减去开始获取锁时间(第一个redis实例开始)作为获取锁消耗总时间。当且仅当redis集群中有一多半锁获取到(n/2+1),并且获取锁总时间小于锁设置的失效时间,才任务该线程获取到分布式锁。
4.获取到锁以后,锁的有效时间更改为,最起初设置的锁失效时间-获取锁总消耗时间
5.如果获取锁失败,应该在redis集群中进行解锁

Reddisson框架有效的实现了对redlock的封装。

1)项目中引入
<!--分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>
2)项目中使用
/**
* redisson配置
*/
@Configuration
public class RedissonConfig {

@Value("${spring.redis.host}")
private String host;

@Value("${spring.redis.port}")
private String port;

@Bean
public RedissonClient getRedisson(){

Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port);
return Redisson.create(config);
}

}

3)代码中引用
public VersionT getOneVersion() {

VersionT versionT = (VersionT) redisCacheTemplate.opsForValue().get("bzversion");
RLock rLock = redissonClient.getLock("redissonLock:" + Thread.currentThread().getName()); //分布式锁,避免大量请求一瞬间请求到数据库,造成缓存击穿
try {
rLock.tryLock(500,10000, TimeUnit.SECONDS); // 锁失效时间设置10秒,锁响应时间设置50毫秒
if (versionT == null) {
VersionT version = versionDao.getOneVersion();
redisCacheTemplate.opsForValue().set("bzversion", version);
return version;
}
} catch (Exception e) {
System.out.println("缓存版本号失败" + e.getMessage());
} finally {
rLock.unlock();
}
return versionT;
}

4)源码中分析(redission获取锁源码解析)
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime); // 单实例获取锁响应时间
long current = System.currentTimeMillis();
long threadId = Thread.currentThread().getId();
Long ttl = this.tryAcquire(leaseTime, unit, threadId); // 获取分布式锁
if (ttl == null) {
return true;
} else {
time -= System.currentTimeMillis() - current; // 超过定义响应时间,返回获取锁失败
if (time <= 0L) {
this.acquireFailed(threadId);
return false;
} else {
current = System.currentTimeMillis();
RFuture<RedissonLockEntry> subscribeFuture = this.subscribe(threadId);
if (!this.await(subscribeFuture, time, TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
subscribeFuture.onComplete((res, e) -> {
if (e == null) {
this.unsubscribe(subscribeFuture, threadId);
}

});
}

this.acquireFailed(threadId);
return false;
} else {
try {
time -= System.currentTimeMillis() - current;
if (time <= 0L) {
this.acquireFailed(threadId);
boolean var20 = false;
return var20;
} else {
boolean var16;
do {
long currentTime = System.currentTimeMillis();
ttl = this.tryAcquire(leaseTime, unit, threadId);
if (ttl == null) {
var16 = true;
return var16;
}

time -= System.currentTimeMillis() - currentTime;
if (time <= 0L) {
this.acquireFailed(threadId);
var16 = false;
return var16;
}

currentTime = System.currentTimeMillis();
if (ttl.longValue() >= 0L && ttl.longValue() < time) {
this.getEntry(threadId).getLatch().tryAcquire(ttl.longValue(), TimeUnit.MILLISECONDS);
} else {
this.getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}

time -= System.currentTimeMillis() - currentTime;
} while(time > 0L);

this.acquireFailed(threadId);
var16 = false;
return var16;
}
} finally {
this.unsubscribe(subscribeFuture, threadId);
}
}
}
}
}
5) <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
this.internalLockLeaseTime = unit.toMillis(leaseTime);
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "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 redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
}
跟踪到这里,就会发现,通过LUA脚本实现了锁判断,锁重入等操作。
if (redis.call('exists', KEYS[1]) == 0)
then redis.call('hset', KEYS[1], ARGV[2], 1); // 获取锁
redis.call('pexpire', KEYS[1], ARGV[1]); // 设置key失效时间
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); // redis重入锁
redis.call('pexpire', KEYS[1], ARGV[1]); // 设置key失效时间
return nil;
end;
return redis.call('pttl', KEYS[1]); // 以毫秒为单位返回 key 的剩余过期时间

redlock已经属于现在较为稳定的reids分布式锁,但是redlock的作者Martin以及antirez就这个算法不足进行了激烈的讨论,以及引发分布式阵营的对垒。
他们主要纠结的问题点在于:
1.时钟发生跳跃
2.长时间的GC pause或者长时间的网络延迟
其实对于时钟跳跃情况1.服务器更新时间插件 2.运维同学手动更改服务器时间 这两种情况虽然很极端,但是确实会造成redlock的失效。
对于第二种情况,无论是长时间的GC pause还是长时间的网络延迟,其实在redlock算法第四步做了校验,那就是最起初设置的失效时间如果小于集群环境下获取redis锁消耗的总时间,则会进行获取锁失败操作。

参考文献 https://mp.weixin.qq.com/s?__biz=MzA4NTg1MjM0Mg==&mid=2657261514&idx=1&sn=47b1a63f065347943341910dddbb785d&chksm=84479e13b3301705ea29c86f457ad74010eba8a8a5c12a7f54bcf264a4a8c9d6adecbe32ad0b&scene=21#wechat_redirect
https://yq.aliyun.com/articles/674394
https://www.cnblogs.com/demingblog/p/9542124.html

Redis分布式锁前世今生的更多相关文章

  1. 利用redis分布式锁的功能来实现定时器的分布式

    文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...

  2. Redis分布式锁

    Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...

  3. redis分布式锁和消息队列

    最近博主在看redis的时候发现了两种redis使用方式,与之前redis作为缓存不同,利用的是redis可设置key的有效时间和redis的BRPOP命令. 分布式锁 由于目前一些编程语言,如PHP ...

  4. redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  5. spring boot redis分布式锁

    随着现在分布式架构越来越盛行,在很多场景下需要使用到分布式锁.分布式锁的实现有很多种,比如基于数据库. zookeeper 等,本文主要介绍使用 Redis 做分布式锁的方式,并封装成spring b ...

  6. Redis分布式锁的正确实现方式

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  7. Redis分布式锁---完美实现

    这几天在做项目缓存时候,因为是分布式的所以需要加锁,就用到了Redis锁,正好从网上发现两篇非常棒的文章,来和大家分享一下. 第一篇是简单完美的实现,第二篇是用到的Redisson. Redis分布式 ...

  8. redis分布式锁实践

    分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...

  9. Redis分布式锁的try-with-resources实现

    Redis分布式锁的try-with-resources实现 一.简介 在当今这个时代,单体应用(standalone)已经很少了,java提供的synchronized已经不能满足需求,大家自然 而 ...

随机推荐

  1. P1013 数素数

    转跳点:

  2. 业务全都在yun上能放心吗?

    导读 组织将其业务在云上进行“全押”,这与扑克游戏中的这个激动人心时刻有着同样的吸引力.这种举动感觉很大胆,但却向外界传达了自己的信心,表明将会果断行动赢得比赛. 大多数银行对处理零售银行业务方式需要 ...

  3. 21 ~ express ~ 内容详情展示 和 阅读数处理

    1,前台 ,/views/main/index.html ,将文章 id 通过url 传送给后台 {% for content in contents %} <div class="p ...

  4. MySLQ排序后标记排行

    查询排行及所有(表名.*) 1. ; AS top, customer.* FROM customer 2. AS top, customer.* ) r, customer ORDER BY cus ...

  5. Javascript里EQ、NE、GT、LT、GE、LE含义

    EQ 就是 EQUAL等于 NE就是 NOT EQUAL不等于 GT 就是 GREATER THAN大于  LT 就是 LESS THAN小于 GE 就是 GREATER THAN OR EQUAL ...

  6. java里mongodb复合查询

    Query query = new Query();Criteria criteria = Criteria.where("packetTitle").is(redPacketSt ...

  7. 从AppleWatch4发布后对手股价大跌看可穿戴市场未来

    万众瞩目的苹果秋季发布会终于落下了帷幕,这场发布会既有惊喜,也有遗憾.遗憾的是新款iPad Pro.廉价版Macbook air没有亮相.iPhone系列价格较贵等,惊喜的则是iPhone的处理器依然 ...

  8. GitHub练习——如何将本地已有项目添加到github

    刚开始开始接触,搞点简单的,看看是怎么把项目传上去,总结一下,大概是这些步骤: 创建本地仓库 将本地仓库变成git可管理的仓库:git init 把项目文件添加到缓存区:项目文件添加到已有的仓库,然后 ...

  9. .NET 软件下面win10自动启动配置

    1.设置所有用户登录都能启动,打开文件夹 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp 2.给要启动的应用程序创建快捷方式, ...

  10. CodeForces 366C 动态规划 转化背包思想

    这道题目昨晚比赛没做出来,昨晚隐约觉得就是个动态规划,但是没想到怎么DP,今天想了一下,突然有个点子,即局部最优子结构为 1-j,j<i,遍历i,每次从所有的1到j当中的最优解里面与当前商品进行 ...