Skip to content

Redis 实现分布式锁

编程中多线程并发和锁的相关应用,尤其是现在应用都是部署在多台服务器上,分布式锁尤其重要。我们可以用 redis 来非常简单地实现分布式锁。其核心思想就是将对锁的操作转换为 redis 操作时,要保障原子性。

用到的两个关键 redis 操作 SETNXGETSET

基础设计

首先让我们自己来构思如何用 redis 实现一个分布式锁。
很简单,在 redis 中存放一个键值对,key 标识锁,value 做其他用途。
当一个线程需要同步操作时:

  1. 首先从 redis 中获取指定 key 的值,
  2. 如果返回 null,说明空闲,当前线程设置 keyvalue 值;如果获取该 key 时如果有值,说明是加锁状态,某个线程正在执行同步操作,当前线程应该挂起等待一段时间再执行。
  3. 当同步操作完成后再删除此 key

这时就要考虑并发情况下对锁的操作了,比如加锁操作包含两步 redis 操作,获取和设值,如果并发情况下两个线程先后交叉执行这两步操作,就会出问题。

  1. 线程一获取 key 的值为 null
  2. 线程二获取 key 的值为 null
  3. 线程一设置 value 为 value1,并认为自己获取到了锁。
  4. 线程二设置 value 为 value2, 并认为自己获取到了锁。

所以我们需要将这两步合为一步,成为原子操作,便可消除这个问题,这就需要上面提到的 SETNX

SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
设置成功,返回 1 。设置失败,返回 0 。

很简单,返回 1 取到锁;返回 0 没有取到锁。

异常状况

上面其实已经解决了分布式锁的最基本的问题,包括对锁的操作。下面,我们就要考虑一些特殊情况了,比如某个线程挂掉或网络问题或其他原因导致没有释放锁,这样一来所有线程就陷入了死锁,为了解决这个问题,我们可以为锁设置超时时间,value 值设置为 当前时间戳+过期时长,当其他线程无法获取锁时,可以查看 value 值是否已过期,如果过期,则可以删除掉 value ,执行上面的争用锁的操作。

  1. 获取 key 的值 value 不为 null,说明有锁
  2. 查看 value 的值是否过期
  3. 如果过期,删除 key
  4. key 值设置为自己的 value,认为获取到锁。

这也会出现上面两个线程交叉执行出现的问题,所以我们需要保障 redis 操作的原子性。
我们需要 GETSET 操作:

将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
当 key 存在但不是字符串类型时,返回一个错误。

当我们判断 value 值过期之后,我们直接用 GETSET 设置新值。如果返回值为原 value ,说明我们设置成功,并获取了锁,如果返回值不为原 value,说明有其他线程抢到了锁。

可重入锁

简单来说,可重入锁就是一个线程获取某个锁之后,可以再次获取该锁。为了实现这一特性,我们可以在 value 值中加入一些标识,比如 UUID,可以让线程确认是不是自己持有的锁。

Published inService

Be First to Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注