阅读了Redis官网分布式锁的文档https://redis.io/topics/distlock,这里做一下笔记
安全性与活跃性的保证
若要有效地使用分布式锁,最少要保证以下几点:
- 互斥。在任意时刻,只能由一个客户端持有锁。
- 无死锁。即使锁定资源的客户端崩溃,其它客户端也可以获得锁。
- 容错。只要大多数Redis节点存活,客户端就可以获取和释放锁。
基于故障切换实现的不足
使用Redis锁住一个资源最简单的方法是在一个实例中创建一个key,并且使用Redis的expires特性指定key的存活时间,当客户端释放资源时,删除这个key即可。
表面上基于以上的分布式锁可以正常工作,但是存在问题:以上Redis是单个实例,如果这个Redis实例宕机了怎么办?我们可能会想,使用Redis的主从模式,加个Slave节点,但是主从模式并不能实现锁的互斥,因为Redis备份数据是异步进行的。
想象一下这个过程:
- 客户端A在master节点获取锁。
- 在master将key写入slave之前,master节点崩溃。
- slave升级为master。
- 客户端B在A持有锁的情况下获得了锁。
在单个Redis实例中的正确实现方式
获取锁使用以下Redis命令:
SET resource_name my_random_value NX PX 30000
这个命令在key不存在的情况下才会设置成功,这个特性是NX参数起作用,PX参数设置key的过期时间。key的值必须在所有客户端中保证唯一性。这种做法是为了以一种安全的方式释放锁:要想删除这个key,必须保证这个key存在且它的值是某个客户端设置的正确的值,才能由该客户端删除key。
下面是Lua脚本实现:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
这种做法避免了一个客户端删除另一个客户端创建的key。
以上是一种获取释放锁的好方法,但这个方法前提是有一个始终可用,安全的实例。
如果在分布式系统中,没有这样的前提保证下,又该如何做。
Redlock算法
在该算法的分布式版本中,假设有5个Redis Master节点。为了获得锁,客户端按以下步骤进行操作:
- 获取当前时间(毫秒)
- 按照一定顺序尝试在所有实例中通过设置相同的key和value获取锁,在每个实例中设置锁的时候,客户端设置key的超时时间要比锁释放的总时间小。例如:释放锁的时间为10秒,则在设置key时的超时时间设置为5-50毫秒。这是为了防止客户端长时间阻塞尝试与Redis节点通信,如果一个节点不可用,应该尝试与下一个节点进行通信。
- 客户端通过从当前时间减去步骤1中记录的时间计算获取锁所用的时间。当且仅当在大多数节点(至少3个)获取到锁,并且获取锁所用的总时间小于锁的有效时间,则视为获取到了锁。
- 如果客户端获取锁失败,则尝试在所有实例中解锁。