敞开成长之旅!这是我参加「日新方案 12 月更文应战」的第 9 天,点击检查活动详情
篇幅太长看着也累,每天前进一点点
前情回忆
分布式锁系列内容规划如下,本篇是第 5 篇:
- 《分布式锁上-初探》
- 《分布式锁中-依据 Zookeeper 的完成是怎样》
- 《分布式锁中-依据 etcd 的完成很高雅》
- 《分布式锁中-依据 Redis 的完成需避坑 – Jedis 篇》
- 《分布式锁中-依据 Redis 的完成怎么防重入》(本篇)
- 《分布式锁中-依据 Redis 的完成许多样 – Redission 篇》(写作中)
- 《分布式锁中-多维度的对比各种分布式锁完成》(写作中)
- 《分布式锁下-分布式锁客户端的抽象、适配与加固》(写作中)
一、背景
昨夜搭档小窗咨询说,当时依据 Jedis 完成的分布式锁,在他们的一个业务场景中不合适,沟通之后了解到他们是想要一个防重入的锁,那搭档所描绘的重入是什么意思呢,看下图:
上图是举例描绘一个重入的场景,有一个恳求被重复提交给 Service-A 了(可能是用户重复点击提交恳求,也可能是 RPC 的重试所触发,也可能是其他的状况),关于 Service-A 来说由于没有幂等机制导致 DB 中插入了多条记录,虽然这种状况很少见,但对 Service-A 说一旦遇到,发生了垃圾数据就会比较费事;因而搭档并不期望相同的恳求由于一些偶发反常,而导致自己发生重复处理、记录。
二、需求
搭档是期望对现有的分布式锁添加防重入的才能,以便达到在某个时刻窗口内,重复多余的恳求只会被处理一次,如下图:
三、分析
怎么将这个才能融入现有的分布式锁组件中呢?收拾之后,给锁添加类型属性,传统的分布式锁若归类为重用锁(A 持锁后,B 来抢锁只需没超时就一直重试抢锁,抢到就用),而这种防重入场景下的锁可了解为”一次性“锁(A 持锁后,B 来抢锁只需锁已存在,则当即放弃),这个归类命名并不权威,仅仅这么区别便利了解。这么区别之后,将新需求收拾如下:
- 运用者在构建锁的时候,可指定锁类型为”一次性“锁,并设定过期时刻,不续租,不自动开释锁,锁过期后会被自动删去;
- 运用者在构建锁的时候,可指定锁类型为”一次性“锁,并设定过期时刻,持锁后会自动续租,若持锁客户端是存活状况则会在完成一个恳求的处理后自动开释锁。若持锁的客户端挂掉了,等锁过期后会自动被删去。但只需锁被开释后,就能够持续抢锁。
四、规划
调整加锁流程的逻辑,当发现锁已经存在后,添加一段逻辑,判别是否是”一次性“锁(下图褐色部分,应该是褐色吧),假如是则当即回来。如此即完成了在某个时刻窗口内,锁是”一次性“的效果。流程图如下:
五、待确认事项
5.1 好像哪里有点不放心
咱们运用的是 SET 指令来完成加锁的逻辑,指令方法如下:
SET键值[NX | XX] [GET] [EX 秒 | PX 毫秒 | EXAT unix 时刻秒 | PXAT unix 时刻毫秒 | 坚持]
1)加锁成功的逻辑是这样:
- 判别 key 是否存在
- 若 key 不存在,就设置 key
- 给 key 指定过期时刻
2)加锁不成功的逻辑是这样:
- 判别 key 是否存在
- 若 key 已存在,则回来
SetParamsparams=SetParams.setParams().nx().ex(lockState.getLeaseTTL());
Stringresult=client.set(lockState.getLockKey(),lockState.getLockValue(),params);
上边代码是之前《分布式锁中-依据 Redis 的完成需避坑 – Jedis 篇》中写的加锁逻辑,其间只依据正常加锁的回来值来判别是否加锁成功,即 result 是不是 “OK”,但 key 已存在导致加锁不成功的回来值到底是什么,应该怎么判别呢?
5.1 SET 的回来值都有什么
在官网中,检查 SET 回来值的描绘,为便利我们,这儿直接贴出结果,应该许多同学都没看过这段描绘吧。
简略字符串回复:
OK
假如SET
正确履行。空回复:
(nil)
假如SET
由于用户指定了NX
或XX
选项但不满意条件而未履行操作。假如指令与
GET
选项一起发出,则上述内容不适用。它会改为如下回复,不管是否SET
实践履行:批量字符串回复:存储在键中的旧字符串值。
空回复:
(nil)
假如密钥不存在。
通过官网给出的描绘能够得知,当时 SET 指令的运用方法,只需回来的不是“OK”,就是锁已存在了,所以将 《分布式锁中-依据 Redis 的完成需避坑 – Jedis 篇》示例中tryLock
的逻辑中,参加一个判别锁类型的逻辑即可,即假如锁 key 已存在,并且锁是”一次性“锁,则不循环等待而是当即回来。
至于这个”一次性“锁的时刻窗口应该是多少则由运用方自行决定。
publicbooleantryLock(longwaitTime,TimeUnitwaitUnit)throwsDtLockException{
longtotalMillisSeconds=waitUnit.toMillis(waitTime);
longstart=System.currentTimeMillis();
//重试,直到成功或超过指定时刻
while(true){
//抢锁
try{
SetParamsparams=SetParams.setParams().nx().ex(lockState.getLeaseTTL());
Stringresult=client.set(lockState.getLockKey(),lockState.getLockValue(),params);
if(RESULT_OK.equals(result)){
manualKeepAlive();
log.info("[jedis-lock] lock success 线程:{}加锁成功,key:{} , value:{}",Thread.currentThread().getName(),lockState.getLockKey(),lockState.getLockValue());
lockState.setLockSuccess(true);
returntrue;
}else{
// 添加判别,假如锁的类型是lockOnce,则当即回来。
//----伪代码 begin -----
if(lockType == lockOnce){
return false;
}
//----伪代码 end -----
if(System.currentTimeMillis()-start>=totalMillisSeconds){
returnfalse;
}
Thread.sleep(sleepMillisecond);
}
}catch(Exceptione){
Throwablecause=e.getCause();
if(causeinstanceofSocketTimeoutException){//忽略网络颤动等反常
}
log.error("[jedis-lock]lockfailed:"+e);
thrownewDtLockException("[jedis-lock]lockfailed:"+e.getMessage(),e);
}
}
}
六、总结
本篇介绍了怎么依据 Redis 的特性来完成一个”一次性的“分布式锁,假如前面几篇都已看过的话,这儿很容易了解。好,本篇就此结束了,感谢您的花费名贵的时刻来读这篇文章,期望能对您有所协助。
另外,请您留意,分布式锁系列内容规划如下,本篇是第 5 篇:
- 《分布式锁上-初探》
- 《分布式锁中-依据 Zookeeper 的完成是怎样》
- 《分布式锁中-依据 etcd 的完成很高雅》
- 《分布式锁中-依据 Redis 的完成需避坑 – Jedis 篇》
- 《分布式锁中-依据 Redis 完成的锁能够供给防重入才能嘛》(本篇)
- 《分布式锁中-依据 Redis 的完成许多样 – Redission 篇》(写作中)
- 《分布式锁中-多维度的对比各种分布式锁完成》(写作中)
- 《分布式锁下-分布式锁客户端的抽象、适配与加固》(写作中)
七、最后说一句(请重视,莫错过)
假如这篇文章对您有协助,或许有所启示的话,欢迎重视微信大众号【 架构染色 】进行沟通和学习。您的支持是我坚持写作最大的动力。
点击链接即可一键三连:重视、点赞、转发。