Redis分布式锁

艺帆风顺 发布于 2025-04-03 26 次阅读


阅读了Redis官网分布式锁的文档https://redis.io/topics/distlock,这里做一下笔记

安全性与活跃性的保证

若要有效地使用分布式锁,最少要保证以下几点:

  1. 互斥。在任意时刻,只能由一个客户端持有锁。
  2. 无死锁。即使锁定资源的客户端崩溃,其它客户端也可以获得锁。
  3. 容错。只要大多数Redis节点存活,客户端就可以获取和释放锁。

    基于故障切换实现的不足

使用Redis锁住一个资源最简单的方法是在一个实例中创建一个key,并且使用Redis的expires特性指定key的存活时间,当客户端释放资源时,删除这个key即可。

表面上基于以上的分布式锁可以正常工作,但是存在问题:以上Redis是单个实例,如果这个Redis实例宕机了怎么办?我们可能会想,使用Redis的主从模式,加个Slave节点,但是主从模式并不能实现锁的互斥,因为Redis备份数据是异步进行的。

想象一下这个过程:

  1. 客户端A在master节点获取锁。
  2. 在master将key写入slave之前,master节点崩溃。
  3. slave升级为master。
  4. 客户端B在A持有锁的情况下获得了锁。

在单个Redis实例中的正确实现方式

获取锁使用以下Redis命令:

  1. SET resource_name my_random_value NX PX 30000

这个命令在key不存在的情况下才会设置成功,这个特性是NX参数起作用,PX参数设置key的过期时间。key的值必须在所有客户端中保证唯一性。这种做法是为了以一种安全的方式释放锁:要想删除这个key,必须保证这个key存在且它的值是某个客户端设置的正确的值,才能由该客户端删除key。

下面是Lua脚本实现:

  1. if redis.call("get",KEYS[1]) == ARGV[1] then
  2.     return redis.call("del",KEYS[1])
  3. else
  4.     return 0
  5. end

这种做法避免了一个客户端删除另一个客户端创建的key。

以上是一种获取释放锁的好方法,但这个方法前提是有一个始终可用,安全的实例。

如果在分布式系统中,没有这样的前提保证下,又该如何做。

Redlock算法

在该算法的分布式版本中,假设有5个Redis Master节点。为了获得锁,客户端按以下步骤进行操作:

  1. 获取当前时间(毫秒)
  2. 按照一定顺序尝试在所有实例中通过设置相同的key和value获取锁,在每个实例中设置锁的时候,客户端设置key的超时时间要比锁释放的总时间小。例如:释放锁的时间为10秒,则在设置key时的超时时间设置为5-50毫秒。这是为了防止客户端长时间阻塞尝试与Redis节点通信,如果一个节点不可用,应该尝试与下一个节点进行通信。
  3. 客户端通过从当前时间减去步骤1中记录的时间计算获取锁所用的时间。当且仅当在大多数节点(至少3个)获取到锁,并且获取锁所用的总时间小于锁的有效时间,则视为获取到了锁。
  4. 如果客户端获取锁失败,则尝试在所有实例中解锁。