Redlock
通常我们可以用SET key value EX seconds NX命令实现一个简单的分布式锁,这种方式适用于只有一个Redis实例的场景,如果有多个Redis实例,比如有一个Master节点和一个Slave节点,那么就会存在问题。
考虑以下场景。
- 客户端A成功向Master申请到了锁。
- 在Master把数据同步到Slave之前Master节点宕机。
- Slave提升为Master。
- 客户端B向Master申请客户端A已经持有的锁,也成功拿到了锁。
上面这个场景的问题是,Master的数据是异步同步到Slave的,如果在同步之前Master发生宕机,那么锁的数据就会丢失,因此客户端A、B就同时持有了这把锁。
在Redlock算法中,要求有N个Master节点,并且这些节点完全独立,没有主从复制或者其它集群机制,客户端按照以下的步骤获取锁。
- 客户端向每个Master节点发送请求,尝试获取锁。在发送请求时设置一个较短的超时时间,防止在向一个已经宕机的节点发送请求时发生长时间的阻塞,比如锁的TTL时间是10秒,那么超时时间可以设置为50毫秒。一旦超时就继续向下一个节点发送请求。
- 如果客户端从半数以上的节点那里获取到了锁,并且在获取锁的过程中消耗的总时间小于锁的TTL时间,就可以认为成功获取了锁。比如成功获取了3个Master节点的锁,并且消耗的时间是500毫秒,小于锁的TTL时间10秒,那么就认为拿锁成功。
- 如果客户端获取锁失败,无论是超时或者获取到的锁的数量没有超过半数节点,客户端都需要向所有的节点发送释放锁的请求,即使没有拿到该节点的锁。在解锁时会判断锁的持有者是否是当前请求的发送方(客户端在加解锁时携带一个随机数作为身份标识),以防止释放了其它客户端持有的锁,这一原子操作没有现成的命令,需要用lua脚本实现。
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
需要注意的是,在下面两种情况下,依然存在多个客户端拿到同一把锁的可能。
- 节点没有开启持久化功能。一旦节点宕机恢复后,数据将全部丢失,此时其它客户端就有可能拿到该节点上的锁,而实际这把锁已经在宕机前被另一个客户端获取。
- 节点开启了持久化功能,但不是每一次写操作都落盘。比如节点虽开启了AOF功能,但是
appendfsync选项设置为everysec,而不是always,那么宕机恢复后也可能会丢失一部分数据,如果这部分丢失的数据包含了锁相关的数据,那么也会出现多个客户端拿到同一把锁的情况。
参考
- 《Distributed locks with Redis》