近两年,微服务越来越流行,越来越多的应用部署在分布式环境中,数据一致性一直是需要关注和解决的问题。分布式锁已经成为一种广泛使用的技术。常用的分布式实现方式有Redis和Zookeeper,其中基于Redis的分布式锁使用更为广泛。但是,我在工作中和网上看到了各种版本的Redis分布式锁实现。每个实现都有一些不精确的部分,甚至可能是错误的实现,包括在代码中。如果不能正确使用分布式锁,可能会在生产环境中造成严重的故障。本文主要梳理了目前遇到的各种分布式锁及其缺陷,并给出了如何选择合适的Redis分布式锁的建议。Redis分布式锁各种版本V1.0tryLock(){SETNXKey1EXPIREKeySeconds}release(){DELETEKey}这个版本应该是最简单的版本,也是使用频率比较高的版本。首先,给锁加一个过期时间操作,避免应用重启服务后或者异常导致无法释放锁,不会出现一直无法释放锁的情况。这个方案的一个问题是,每次提交Redis请求,如果应用出现异常或者执行完第一个命令后重启,锁不会过期。一种改进的方案是使用Lua脚本(包括SETNX和EXPIRE命令),但是如果Redis只执行一条命令然后崩溃或者发生主从切换,仍然会出现锁没有过期时间,最终会不被释放。还有一个问题是,很多同学在释放分布式锁的过程中,不管是否获取锁成功,都在finally中释放了锁。这是对锁的错误使用。该问题将在后续的V3.0版本中得到解决。解决锁无法释放的问题是基于GETSET命令实现V1.1基于GETSETtryLock(){NewExpireTime=CurrentTimestamp+ExpireSecondsif(SETNXKeyNewExpireTimeSeconds){oldExpireTime=GET(Key)if(oldExpireTime=N/2+1)获取到锁,并且获取锁花费的总时间不超过锁有效时间(lockvaliditytime),则客户端认为最终获取了锁定成功;否则,认为最终获取锁失败。如果最终获取锁成功,则需要重新计算锁的有效时间,等于原锁的有效时间减去第3步计算的获取锁所消耗的时间。如果最终获取锁失败(可能是因为获取锁的Redis节点数小于N/2+1,或者整个获取锁的过程耗时比锁的初始有效时间长),客户端应该立即向所有的节点发送请求Redis节点发起释放锁的操作(即前面介绍的RedisLua脚本)。释放锁:对所有Redis节点发起释放锁操作。但是MartinKleppmann对这个算法提出质疑,提出应该基于fencingtoken机制(每次资源操作都需要token校验)。1.Redlock在系统模型中尤为重要针对分布式时钟一致性问题提出一个假设。在实际场景中,存在时钟不一致和时钟跳变的问题,而Redlock恰恰是一种基于时序的分布式锁。2.另外,由于Redlock是基于自动过期机制,所以还是没有解决长期问题。gcpause等问题导致的锁自动失效,带来安全问题。随后antirez回复了MartinKleppmann的问题,给出了过期机制的合理性,以及在实际场景中如何处理因暂停问题导致多个客户端同时访问资源的情况。总结无论是基于SETNX版本的Redis单实例分布式锁还是Redlock分布式锁,都是为了保证以下??特性1.安全性:不允许多个客户端同时持有锁2.主动死锁:lock最终应该是即使客户端崩溃或者网络分区发生也能被释放(一般是基于超时机制)。容错性:只要半数以上的Redis节点可用,就可以正确获取和释放锁。因此,在保证安全性和活动性的过程中,有必要开发或使用分布式锁,以避免不可预知的结果。另外,每个版本的分布式锁都存在一些问题。在使用锁具时,应根据锁具的实际使用场景选择合适的锁具。通常,锁的使用场景包括:Efficiency(效率):只需要一个Client即可完成操作,不需要重复执行,这是一个松散的分布式锁,只需要保证锁的活动性;Correctness(正确性):多个Client保证严格互斥,不允许同时持有锁或者同时操作同一个资源,这种场景下,锁的选择和使用需要更加严格,同时在业务代码上尽量做到幂等。Redis分布式锁的实现还有很多问题有待解决。我们需要认识到这些不清楚如何正确实现一个Redis分布式锁的问题,进而在工作中合理的选择和使用分布式锁。