首页 > 编程知识 正文

分布式锁服务,分布式锁是什么

时间:2023-05-05 13:59:13 阅读:146944 作者:1175

需要锁定的条件:

多任务环境下。 (进程、线程)任务都是对同一共享资源进行写入。 对资源的访问是互斥的。 运营周期:

竞争锁定。 在获取锁定之前无法操作资源。 占有锁具。 正在操作。 其他竞争对手,任务已排满。 占有锁的人,解除锁。 从1开始继续。 JVM锁定无法解决分布式环境中的封锁问题。

手写Redis锁: Redis :存储在内存中的数据结构服务、内存数据库。 已启用:数据库、缓存和消息队列。 采用单线程模型,并发性强。 10万同时没关系。

分布式锁定知识:

redis单进程单线程。

缓存有效期。 到了有效期,删除数据。

setnx。 只有在key存在、不执行任何操作、key不存在时才设置。

《Redis 分布锁》

单节点

加锁

SET orderId driverId NX PX 30000

如果上述命令成功运行,客户端将成功获取锁定,并且可以访问共享资源。 如果上述命令执行失败,则表示获取锁定失败。

释放锁

关键是判断是不是自己戴的钥匙。

关注点

orderId是我们的钥匙,锁定的目标。

driverId是我们的司机ID,必须确保在足够的时间内所有客户端的所有锁获取请求是唯一的。 也就是说,订单被司机抢走了。

NX表示只有在orderId不存在时SET才能成功。 这样,只有第一个请求的客户端才能获取锁定,而其他客户端只有在解锁后才能获取锁定。

PX 30000表示此锁定有30秒的自动过期时间。 当然,这里30秒是一个例子,客户端可以选择合适的过期日期。

此锁定必须设置有效期限。 否则,如果一个客户端获得锁定后崩溃,或者由于发生网络分区而无法与Redis节点进行通信,则锁定将保持不变,其他客户端将无法获得锁定。 antirez在以后的分析中也特别强调这一点,并且将该有效期限称为锁定的有效时间(lock validity time )。 获得锁定的客户端必须在此时间内完成对共享资源的访问。

此操作不可分割。

setnxorderiddriveridexpireorderid 30提供的效果与上述算法说明中的set命令相同,但不是原子命令。 如果客户端在运行SETNX后崩溃,则没有机会运行EXPIRE,并保持此锁定。 发生死锁。 必须将key设置为value。 value保证每个线程都不同。 如果value在每个线程之间相同。 发生误解锁定的问题。

1 .客户端1成功获取了锁定。 2 .客户端1在某个操作中屏蔽了很长时间。 3 .有效期到了,锁自动释放了。 4 .客户端2获取了对应于同一资源的锁定。 5 .客户端1从块中恢复,并释放客户端2拥有的锁定。 此后,客户端2在访问共享资源时未锁定以保护它。 解除锁定的操作,必须解除自己戴的锁定。

1 .客户端1成功获取了锁定。 2 .客户端1访问共享资源。 3 .客户端1通过执行“获取”操作获取随机字符串值以解除锁定。 4 .客户端1判断为随机字符串的值等于预期值。 5 .客户端1因某种原因屏蔽了很长时间。 6 .有效期到了,锁自动释放了。 7 .客户端2获得了对应于同一资源的锁定。 8 .客户端1从块中恢复,执行DEL操作,解除客户端2拥有的锁定。 redis故障问题。

如果redis发生故障,则所有客户端都将无法获取锁定,服务将不可用。 为了提高可用性。 我们在redis上安排主仆。 如果主机不可用,系统将切换到slave。 由于Redis主从复制是异步的,因此可能会丢失锁定安全性。

1 .客户端1从主机获取了锁定。 2 .大师瘫痪了。 存储锁的密钥还没有时间同步到Slave。 3.Slave升级到主服务器。 4 .客户端2从新的Master获取了对应于同一资源的锁定。 客户端1和客户端2同时具有相同资源的锁定。 锁的安全性被打破了。

该算法中出现的“锁定有效时间”(lock validity time )应该设定为多少? 如果设置太短,则客户端在完成对共享资源的访问之前锁定可能已过期,可能会失去保护;如果设置太长,则解锁锁定的客户端失败,所有其他客户端将无法获取锁定,并且功能会持续很长时间如果设置得稍短,并且线程具有锁定,则必须打开线程以自动延长有效期。

还有一点,如果在过期时间内,程序没有执行完,是不能让key过期的,所以要延时。

RedLock(多master)目的:独占访问共享资源。

因此,antirez提出了一种新的分布式锁定算法Redlock,其基于n个完全独立的Redis节点(通常n可以设置为5 )。

运行Redlock算法的客户端通过执行以下步骤获取锁定:

获取当前时间(毫秒)。 向着顺序

N个Redis节点执行 获取锁 的操作。这个获取操作跟前面基于单Redis节点的 获取锁 的过程相同,包含value driverId ,也包含过期时间(比如 PX 30000 ,即锁的有效时间)。为了保证在某个Redis节点不可用的时候算法能够继续运行,这个 获取锁 的操作还有一个超时时间(time out),它要远小于锁的有效时间(几十毫秒量级)。客户端在向某个Redis节点获取锁失败以后,应该立即尝试下一个Redis节点。这里的失败,应该包含任何类型的失败,比如该Redis节点不可用,或者该Redis节点上的锁已经被其它客户端持有(注:Redlock原文中这里只提到了Redis节点不可用的情况,但也应该包含其它的失败情况)。计算整个获取锁的过程总共消耗了多长时间,计算方法是用当前时间减去第1步记录的时间。如果客户端从大多数Redis节点(>= N/2+1)成功获取到了锁,并且获取锁总共消耗的时间没有超过锁的有效时间(lock validity time),那么这时客户端才认为最终获取锁成功;否则,认为最终获取锁失败。如果最终获取锁成功了,那么这个锁的有效时间应该重新计算,它等于最初的锁的有效时间减去第3步计算出来的获取锁消耗的时间。如果最终获取锁失败了(可能由于获取到锁的Redis节点个数少于N/2+1,或者整个获取锁的过程消耗的时间超过了锁的最初有效时间),那么客户端应该立即向所有Redis节点发起 释放锁 的操作。

当然,上面描述的只是 获取锁 的过程,而 释放锁 的过程比较简单:客户端向所有Redis节点发起 释放锁 的操作,不管这些节点当时在获取锁的时候成功与否。

问题:

由于N个Redis节点中的大多数能正常工作就能保证Redlock正常工作,因此理论上它的可用性更高。我们前面讨论的单Redis节点的分布式锁在failover的时候锁失效的问题,在Redlock中不存在了,但如果有节点发生崩溃重启,还是会对锁的安全性有影响的。具体的影响程度跟Redis对数据的持久化程度有关。

假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:

客户端1成功锁住了A, B, C, 获取锁 成功(但D和E没有锁住)。节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。节点C重启后,客户端2锁住了C, D, E, 获取锁 成功。

这样,客户端1和客户端2同时获得了锁(针对同一资源)。

在默认情况下,Redis的AOF持久化方式是每秒写一次磁盘(即执行fsync),因此最坏情况下可能丢失1秒的数据。为了尽可能不丢数据,Redis允许设置成每次修改数据都进行fsync,但这会降低性能。当然,即使执行了fsync也仍然有可能丢失数据(这取决于系统而不是Redis的实现)。所以,上面分析的由于节点重启引发的锁失效问题,总是有可能出现的。为了应对这一问题,antirez又提出了 延迟重启 (delayed restarts)的概念。也就是说,一个节点崩溃后,先不立即重启它,而是等待一段时间再重启,这段时间应该大于锁的有效时间(lock validity time)。这样的话,这个节点在重启前所参与的锁都会过期,它在重启后就不会对现有的锁造成影响。

关于Redlock还有一点细节值得拿出来分析一下:在最后 释放锁 的时候,antirez在算法描述中特别强调,客户端应该向所有Redis节点发起 释放锁 的操作。也就是说,即使当时向某个节点获取锁没有成功,在释放锁的时候也不应该漏掉这个节点。这是为什么呢?设想这样一种情况,客户端发给某个Redis节点的 获取锁 的请求成功到达了该Redis节点,这个节点也成功执行了 SET操作,但是它返回给客户端的响应包却丢失了。这在客户端看来,获取锁的请求由于超时而失败了,但在Redis这边看来,加锁已经成功了。因此,释放锁的时候,客户端也应该对当时获取锁失败的那些Redis节点同样发起请求。实际上,这种情况在异步通信模型中是有可能发生的:客户端向服务器通信是正常的,但反方向却是有问题的。

 

 

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。