分布式锁用redis实现,有一定数量的库和博客帖子描述怎么用redis实现分布式锁DLM(Distributed Lock Manager),以下提供一个典型的DLM实现算法实现,这个算法叫Redlock
,DML是比单实例的方式更加安全。官网
安全性和活跃度保证
以下三个指标是衡量锁的高可用保证。
- 1.安全属性:互斥锁。在任何时候,仅仅一个客户端能够持有一个锁;
- 2.活跃度属性A:无死锁。即使锁定资源的客户机崩溃或被分区,最总总是能获得一个锁;
- 3.活跃度属性B:容错。只要redis的大部分节点都是健康的,客户端是能够释放和获取锁。
为什么基于容错的实现是不行的
主要的原因是:Redis副本同步是异步地(数据没有同步到备份节点)。具体如下:
- 1.客户端A在master获得锁;
- 2.在写这个key到slave节点时,master崩溃;
- 3.slave升为master;
- 4.客户端B获得的锁和客户端A持有的是同一把锁。 使用这种方式,在错误产生时,多个客户端在同一个时刻持有相同的一把锁。如果你是要在这种场景下使用,可以基于容错机制来实现锁,否则,我建议你用DML实现锁。
用单实例正确的实现
在克服上面单例的限制,现在正确的实现。获取锁:
SET resource_name my_random_value NX PX 30000
这个命令仅仅在key不存在时才执行,用一个30000毫秒的超时,key的值是随机字符串,这个值必须是跨域所有的客户和锁请求是独一无二的。基于随机值是为了释放锁是安全的。
用如下的脚本告诉redis,仅仅在key存在并且key的值等于期望的值才移除这个key:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
为了避免移除另外一个客户端创建的锁。例如:一个客户端可能获得锁,在执行一些操作阻塞时超过了锁的有效时间(key的过期时间),之后移除这个key,这个key已经被另外一个客户端获取使用。一个客户端使用DEL
是不安全的,可能移除另外一个客户端的锁,用上面的实现替换每一个锁的签名(用一个随机字符串),这样锁只能被创建它的客户端移除。
随机数,通常的业务场景下,可以使用unix系统时间(微秒)加上客户端标识符。
key的TTL(生存时间),即是自动释放时间,也是客户端执行操作必要的时间(另外一个客户端获得锁时间)
这是一个安全、单实例、总是可获得锁和释放锁的案例。
Redlock算法实现(分布式锁实现)
假设有5个独立的master节点,不使用副本。
为了获得锁,客户端执行下面的操作:
- 1.获得当前的时间(微秒)
- 2.客户端试图在所有N个实例中依次获取锁,在所有的实例中使用相同的key和随机值。在第2步,在每个实例设置锁,客户端使用一个与锁自动释放总时间相比很小的超时来获取它。例如:自动自动释放是10秒,超时可能设置在5~50微秒范围。 这可以防止客户端长时间保持阻塞试图与一个宕机的Redis节点建立会话:如果一个实例不可获得,将会尝试尽快与下一个实例建立会话。
-
- 客户端计算为了获得锁花费了多少时间,用当前时间戳减去在步骤1中获得的时间戳。当且仅当客户端能够在大多数实例(至少3个)中获得锁,并且获取锁的总时间小于锁有效时间时,则认为该锁已被获取。
- 4.如果锁可以获得,他的有效时间是初始的有效的时间减去消逝的时间,和步骤3中计算的那样。
- 5.如果客户端由于某些原因无法获得锁(要么无法锁定N/2+1个实例,要么有效时间为负数),它将尝试解锁所有实例(甚至是它认为自己无法锁定的实例)。