分布式锁
使用redis实现
普通实现:
setnx expire? 非原子操作 setnx后 宕机 没有设置过期时间 造成死锁
set nx px 原子操作 同时设置key值与过期时间
一个客户端通过set nx px 命令首次完成加锁,注意这个值一定要是唯一的,开始执行自己的逻辑
另外的客户端获取锁 如果锁还在有效期,则获取失败,否则超时后 key删除 则可以获取到新锁
会有一些问题:
expire时间的设置。 客户端执行自己的逻辑不知道要多久,那这个过期的时间需要设置成为多少才能够合理呢,如果设置的时间过短,锁过期了,我们客户端还在执行逻辑,但是这时其他客户端业务已经获取到了锁,并且开始了新的逻辑,如果这个时候前一个客户端自顾自的把锁释放了,那就相当于是把第二个客户端的锁释放了,这时候又有别的客户端可以来获取到锁,形成了一种恶性循环。所以 提出了一种解决方案,我们在value中可以增加自己一个独有的IDversion字段值,每次解锁时,先匹配IDversion字段是否相等,就是说先判断是不是自己的客户端的锁,如果是的话,再去解锁。但是还有一个问题,那我过期时间太短,我的确没执行完,我还要需要持有锁。所以我们可以开启一个监控续约任务,始终监控这个过期时间,如果这个key即将过期了,但是客户端的执行逻辑还没有执行完毕,就对这个key 的过期时间进行续约,这样就可以保证在客户端执行期间,这个锁不被释放。
还有一个问题,如果我们在执行过程中,set nx px 在master上执行成功了,但是由于主从的消息复制是异步的,所以可能在某一刻,从节点还没有同步这个操作,但是master宕机了。这个key可能就会有丢失的情况,然后从节点被晋升为master节点,另一个客户端来到之后,请求锁成功了,但是这时第一个客户端也持有锁,就造成了两个客户端同时访问共享资源的情况。所以只能在单机redis 上使用,集群架构下会出现问题。
针对集群架构下的问题,redis给出了一种叫做redlock的解决方案。
relock步骤:
首先获取当前时间,
按顺序依次向N个redis cluster节点执行获取锁的操作,在一个节点上的获取锁的过程和之前单节点上获取锁加锁的过程相同,包含过期时间。为了保证这个过程保证能够在某个reids节点不可用时算法可以继续运行这个获取锁的操作会有一个超时时间,远远小于锁的过期时间,客户端在某个节点获取锁失败后,应立即尝试下一个节点。
计算整个获取锁的过程总共消耗了多少时间,计算方法是当前时间减去第一步记录的时间,如果客户端从大多数redis节点都成功获取到了锁,并且获取锁的总共消耗时间没有超过锁的有效时间,那么这个时候才认为最终客户端获取锁成功,否则认为获取锁失败。如果最终获取锁失败了,就应该向所有redis节点发起释放锁的操作。
存在的问题:
redlock方案解决了集群架构下的异步复制存在的问题,但是还是存在一些问题。
首先时间问题,如果我们总获取锁的时间耗时太多,留给客户端的执行时间也就是持有锁的时间很短,客户端来不及完整执行完毕自己的逻辑,这种情况下要如何选择是一个问题。
还有一个持久化的问题,如果一个节点宕机了,但是客户端在这个节点上获取到了锁但没有被持久化下来,另外客户端就还可能获得这个节点上的锁,造成同时获取一个锁。
zk分布式锁
ZK是一个为分布式应用提供一致性服务的开源组件,内部是一个分层的文件系统目录树结构,规定同一目录下只能有一个唯一文件名。
创建一个目录mylock
一个线程想要获取锁就要在mylock目录下创建临时顺序节点,
获取mylock’目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,说明当前线程顺序号最小获得锁
新的线程到来想要获取锁 就要获取所有的节点,判断自己是不是最小及诶单,监听比自己小的节点
线程A处理完,删除自己的节点,线程B监听到变更时间,判断自己是不是最小的节点,如果是则获得锁
缺点 需要频繁的创建和删除节点,性能上不如redis,但是不存在redis哪些问题,可靠性更好。
redis和ZK选择
set nx px + id +lua脚本
Redis本身性能就很高,因此实现的分布式锁效率比较高,性能比较高,但是可靠性在分布式节点下有问题
redlock
需要对多个节点轮询,降低了一些效率,过期时间不好控制,还有一些问题没有解决
zk
不存在redis的问题,可靠性很高,频繁创建和删除节点性能不如redis
理解和实现的难易程度: redis< zk
性能 redis > zk
可靠性 zk > redis