常见的分布式锁实现方案:ZK分布式锁、Redis分布式锁
ZK分布式锁:
原理:使用ZK 的临时有序节点、节点的监听机制来实现的。
锁特点:悲观锁,公平锁
获取锁:客户端A在/mylock节点目录下创建临时有序ZNode,创建成功,并且发现自己是第一个ZNode,那么获取锁成功。
等待锁:客户端B在/mylock节点目录下创建临时有序ZNode,创建成功,但发现自己并不是第一个ZNode,那么获取锁失败,注册一个监听器,监听前一个ZNode的删除事件。
释放锁:客户端A使用完毕之后,删除这个临时有序ZNode即可释放锁。
再次获取锁:客户端B监听到前一个节点的删除事件,然后再去判断自己是否是第一个ZNode,发现是,那么获取锁成功。
Redis分布式锁:
Redis分布式锁的演进过程:
SETNX mylock value
获取锁:如果mylock这个key不存在,则set成功,成功获取锁。
等待锁:如果set失败,通过轮询不断来查看。
缺点:如果应用A获取锁之后,释放锁之前,挂了,那么这个mylock key 将不能删除,mylock 这把锁将得不到释放,其他应用将一直等待下去!
SET mylock value NX PX 30000
获取锁:如果mylock这个key不存在,则set成功,成功获取锁,同时这把锁有30秒钟的时间限制,超时则自动释放。通过watch dog后台线程不断查看,如果当前客户端还持有锁mylock,那么将延长锁mylock的生存时间。
等待锁:如果set失败,则会获取当前key的生存时间,通过不断轮询来查看。
缺点:当应用A获取锁之后,由于某些原因,应用B也获取了该锁,但应用A处理完成之后,去释放锁了:del mylock,不仅释放了应用A的锁,同时也释放了属于应用B的锁!
SET mylock 随机值 NX PX 30000
获取锁:如果mylock这个key不存在,则set成功,成功获取锁,同时这把锁有30秒钟的时间限制,超时则自动释放。通过watch dog后台线程不断查看,如果当前客户端还持有锁mylock,那么将延长锁mylock的生存时间。
等待锁:如果set失败,则会获取当前key的生存时间,然后通过不断轮询来查看。
释放锁:释放锁时需要去比对客户端保存的value值是否等于此随机值,只有相等的时候;才能成功释放锁,否则什么也不做。
缺点:只是对一个redis的一个master节点加锁,如果master挂了,由于redis采用的是异步复制的方式,那么此时很有可能锁的信息并没有复制到slave中,导致锁信息的丢失。
锁特点:悲观锁,非公平锁,可重入锁,可超时锁
具体机制可以参考这篇博客:Redis分布式锁的实现原理 - 割肉机 - 博客园
RedLock算法:
redlock算法应用于多master的redis集群场景下。这个场景是假设有一个 redis cluster,有 5 个 redis master 实例。然后执行如下步骤获取一把锁:
1、获取当前时间戳,单位是毫秒,使用上面的方式,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒,只有在过期时间之内在某个节点上创建锁,才算在某个节点上尝试成功;
2、尝试在大多数节点上建立一个锁,比如 有5 个master节点,就要求在 3 个master节点上都尝试成功之后才行 ;
3、要是锁建立失败了,那么就依次删除之前建立过的锁;
4、只要别人建立了一把分布式锁,你就得不断轮询去尝试获取锁。
Redis 官方给出了以上两种基于 Redis 实现分布式锁的方法,详细说明可以查看:Distributed locks with Redis – Redis
Redis分布式锁 VS ZK分布式锁
锁释放对比:
持有Redis分布式锁的客户端如果挂了,需要等待超时时间之后,锁才会被释放;
持有ZK分布式锁的客户端如果挂了,因为创建的是临时ZNode,所以当ZK的心跳机制判定客户端挂了,临时ZNode自动删除,锁释放。
锁等待过程对比:
Redis分布式锁,当获取锁失败后,需要客户端不断去轮询,比较消耗性能;
ZK分布式锁,当获取锁失败后,注册对前一个ZNode删除事件的监听器即可,当前一个节点被删除之后就会通知应用程序,来重新尝试获取锁。
思考:
Redis分布式锁
为什么Redis分布式锁面临了那么多问题:假如客户端挂了怎么办?任务执行时间超过key设定的时间怎么办? 当master挂了,锁信息丢失怎么办?
其实我觉得master本身并不适合存储分布式锁信息,原因有两个:
1、Redis客户端和服务端并没有心跳机制监控,所以有锁得不到释放的可能。正是因为如此,Redis把锁加了一个超时时间,避免获取锁的客户端挂了的情况下锁得不到释放;同时为了避免设定的锁超时时间不够用,还需要一个watch dog的自动延期机制。
2、Redis集群是异步复制的方式,这没有什么问题,因为Redis本身就只是存储缓存数据而已,可以容忍一定范围的数据的丢失,但是如果存储的是锁信息的话,这个信息就是绝对不能丢失的了。正是因为如此,Redis才有了所谓RedLock算法,通过多master来保证锁数据的绝对不丢失。所以如果你使用的redis集群是单master,则要好好考虑一下是否可以用redis来做分布式锁了。
ZK分布式锁:
反观ZK分布式锁就很简单,就是利用了两个特性:临时顺序节点,节点的监听机制。
ZK分布式锁并不存在Redis锁的问题:
不存在锁得不到释放的问题:ZK与客户端有心跳机制,如果客户端挂了,那么临时有序节点也就会自动删除,锁也就自动释放;
不存在锁信息丢失的问题:ZK本身就是支持CP的,我猜想ZK肯定使用的是同步复制的方式,优先保证leader与follower信息的一致性,此时即使leader挂了,锁信息也不会丢失。