这篇文章主要介绍”复述,怎么实现分布式锁”,在日常操作中,相信很多人在复述,怎么实现分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答“复述,怎么实现分布式锁”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
复述,命令介绍
使用复述,实现分布式锁,有两个重要函数需要介绍
SETNX命令(如果不存在组)
语法:
SETNX键值
功能:
当且仅当关键不存在,将关键的值设为值,并返回1;若给定的关键已经存在,则SETNX不做任何动作,并返回0。
GETSET命令
语法:
GETSET键值
功能:
将给定键的值设为值,并返回关键的旧值(旧值),当关键存在但不是字符串类型时,返回一个错误,当关键不存在时,返回零。
得到命令
语法:
得到关键功能:
返回键所关联的字符串值,如果关键不存在那么返回特殊值零。
DEL命令
语法:
DEL键[关键…]
功能:
删除给定的一个或多个键,不存在的钥匙会被忽略。
兵贵精,不在多。分布式锁,我们就依靠这四个命令。但在具体实现,还有很多细节,需要仔细斟酌,因为在分布式并发多进程中,任何一点出现差错,都会导致死锁,保持住所有进程。
加锁实现
SETNX可以直接加锁操作,比如说对某个关键词foo加锁,客户端可以尝试
SETNX foo。锁& lt;当前unix time>
如果返回1,表示客户端已经获取锁,可以往下操作,操作完成后,通过
DEL foo.lock
命令来释放锁。
如果返回0,说明foo已经被其他客户端上的锁,如果锁是非堵塞的,可以选择返回调用。如果是堵塞调用调用,就需要进入以下个重试循环,直至成功获得锁或者重试超时。理想是美好的,现实是残酷的。仅仅使用SETNX加锁带有竞争条件的,在某些特定的情况会造成死锁错误。
处理死锁
在上面的处理方式中,如果获取锁的客户端端执行时间过长,进程被消灭掉,或者因为其他异常崩溃,导致无法释放锁,就会造成死锁,所以,需要对加锁要做时效性检测,因此,我们在加锁时,把当前时间戳作为价值存入此锁中,通过当前时间戳和复述中的时间戳进行对比,如果超过一定差值,认为锁已经时效,防止锁无限期的锁下去,但是,在大并发情况,如果同时检测锁失效,并简单粗暴的删除死锁,再通过SETNX上锁,可能会导致竞争条件的产生,即多个客户端同时获取锁。
C1获取锁,并崩溃.C2和C3调用SETNX上锁返回0后,获得foo。锁的时间戳,通过比对时间戳,发现锁超时。
C2向foo。锁发送DEL命令。
C2向foo。锁发送SETNX获取锁。
C3向foo。锁发送DEL命令,此时C3发送▽时,其实DEL掉的是C2的锁。
C3向foo.lock发送SETNX获取锁。
此时C2和C3都获取了锁,产生竞争条件,如果在更高并发的情况,可能会有更多客户端获取锁,所以,德尔锁的操作,不能直接使用在锁超时的情况下,幸好我们有GETSET方法,假设我们现在有另外一个客户端C4,看看如何使用GETSET方式,避免这种情况产生。
C1获取锁,并崩溃.C2和C3调用SETNX上锁返回0后,调用得到命令获得foo。锁的时间戳T1,通过比对时间戳,发现锁超时。
C4向foo。锁发送GESET命令,
GETSET foo。锁& lt;当前unix time>
并得到foo.lock中老的时间戳T2
如果T1=T2,说明C4获得时间戳。
如果T1 !=T2,说明C4之前有另外一个客户端C5通过调用GETSET方式获取了时间戳,C4未获得锁。只能睡下,进入下次循环中。
现在唯一的问题是,C4设置foo.lock的新时间戳,是否会对锁产生影响。其实我们可以看到C4和C5执行的时间差值极小,并且写入foo。中锁的都是有效时间错,所以对锁并没有影响。
为了让这个锁更加强壮,获取锁的客户端,应该在调用关键业务时,再次调用得到方法获取T1,和写入的T0时间戳进行对比,以免锁因其他情况被执行DEL意外解开而不知。以上步骤和情况,很容易从其他参考资料中看到。客户端处理和失败的情况非常复杂,不仅仅是崩溃这么简单,还可能是客户端因为某些操作被阻塞了相当长时间,紧接着德尔命令被尝试执行(但这时锁却在另外的客户端手上)。也可能因为处理不当,导致死锁。还有可能因为睡眠设置不合理,导致复述,在大并发下被压垮。最为常见的问题还有
得到返回零时应该走那种逻辑?
第一种走超时逻辑
C1客户端获取锁,并且处理完后,德尔掉锁,在德尔锁之前.C2通过SETNX向foo。锁设置时间戳T0发现有客户端获取锁,进入得到操作。