需要设计合理分布式锁,并满足基本的保障 1.安全 -> 互斥属性,在任何条件下,只允许一个客户端拿到锁 2.活跃度(可靠性)A -> 死锁检测,即使有获取到锁的客户端崩溃或者不可用,但是最终锁还是能被获取到 3.活跃度(可靠性)B -> 容错,只要大部分的 redis 节点都存活,客户端就能够获取锁和释放锁 复制代码
为什么故障恢复的实现还不足够
有个Master + slave 1.客户端A获取锁 2.master 在命令传播给slave前就崩溃了 3.slave这时候还不存在key,所以当它被晋升成master时 4.客户端B尝试获取锁,就被获取到了-> 这时候就不满足redis分布式锁的安全性了 在极端情况下,集群服务发生失败时,多客户端可能同时获取锁 复制代码
单实例的正确实现
在解决上述问题前,先把基本的redis分布式锁的设计做好
当我们获取锁的时候,执行下面命令行:
SET resource_name my_random_value NX PX 30000 NX表示当key不存在才能设置成功 PX表示超时时间 my_random_value 需要在所有客户端和获取锁的请求中表示唯一 复制代码
释放锁,需要带着 my_random_value 作标识然后再del,2个操作作原子,所以推荐使用 lua 脚本
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end 复制代码
说明
1.这样就能有效的阻止其他客户端生成锁。(应该不用多说明吧。) 2.my_random_value 应该怎么设置,反正容量越小,消耗越小越好,文档上给出是使用clientId加上时间戳或者使用RC4算法 3.expire的时间设置,我们称作锁有效时间,是锁自动释放时间 + 客户端需要在锁时间内执行的事务所需要的时间(在其他客户端获取到锁之前) = 锁自动释放时间 + 客户端处理业务(锁期间)的时间 考虑到互斥的保证,这个时间窗口应该限制在锁被获取之后 复制代码
基本操作介绍了,现在可以优化了
Redlock算法
在分布式版本中,我们假设我们有N个redis 的matser,各自独立,没有任何关系(不在一个cluster下),可以部署在不同的服务器或是虚拟机上,假设N设置成5 客户端获取锁的操作: 1. 客户端A获取当前时间戳 2. 客户端A尝试在所有N个redis中获取锁(有序的),使用同样的key和value,这一步中,由于需要遍历去请求多个redis服务(set的命令请求,之前理解成业务的处理时间),可能导致阻塞,需要设置超时时间,假设锁自动释放的时间是10s,那么这个超时时间可以设置在 5-50毫秒范围,这一步,防止客户端在获取锁期间由于redis节点崩溃不可用导致获取锁曹氏 3. 客户端A获取锁时,再获取当前时间戳,与步骤1的时间相减,得到获取锁消耗的时间,在锁有效期内,当且仅当客户端A拿到大部分(至少3)的锁时,分布式锁才可以被正式获取 4. 每次当锁被获取到时(从每个master获取),有效时间可以被设置成初始有效时间减去获取锁消耗的时间 (因为已经获取锁了,所以获取锁的时间不用算进去) 5. 假设在步骤2中客户端A获取不到锁,比如拿不到大部分的锁,或者是锁还没超时,它需要把自己在少部分redis上拿到的锁释放掉 复制代码
这个算法是异步的吗
这个算法依赖的一个假设:是不同进程的各自的时钟精确率(就是表走得快走得慢,而且误差跟锁释放时间比小得多)一样,而且不(需要)作时间的同步。类似于,现实生活中,每个人都各自使用自己的电脑(以及电脑上的时间),通常也不会有什么问题; 在这个情况下,我们需要更具体的说明互斥规则:它(互斥规则)保证仅仅只有获取锁的哭护短能够在锁的有效时间内结束它的工作,减去某个很小的时间差(几毫秒,用来作补偿) 复制代码
失败重试(这里指的是获取锁的失败)
当客户端获取不到锁,它应该在之后一个随机时间点重试,这为了避免多个客户端尝试同时获取同一个资源(类似脑裂的情况,大概意思就是竞争了一堆,却发现,没人拿到锁),客户端拿到锁越快(早),脑裂的情况越小(或者重试的需要越小),所以理想情况下,客户端可以同时(多路复用)发送set命令给各个master 复制代码
释放锁
锁释放步骤很简单,就是把所有master实例上的锁释放,并不需要关心客户端在该实例上有没有成功得到锁 复制代码
安全讨论
假设一个客户端能够获取大多数实例上的锁,所有实例都会存在一个key,并且有个相同的超时时间,但是这个key的设置的时间点是不一样的(就是set的时候都不一样,因为是顺序执行,总会有时间差),所以key会在不同的时间超时。但是当第一个key被至少设成T1,最后一个key被设成T2(超时时间),我们可以确认第一个key会存在至少 MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT 的时间,而且其他的key会失效的更晚,我们要同时将时间设置成这个 当一个key被set(NX)的时候,其他key就无法被别的客户端set了 ---- 总的说的就是时间设置还有并发控制 复制代码
可靠性讨论
1. 自动释放的锁最终还能被获取到; 2. 客户端通常会协助删除那些没获取到的锁(步骤5)、锁获取到并且工作结束的,使得并不需要等待锁超时才能重新获取锁; 3. 客户端需要重试获取锁,为了资源竞争,他们重试的等待时间应该大于需要从大多数实例上获取的时间 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。