SETNX (已弃用)

自Redis版本2.6.12起,此命令被视为已弃用。

在迁移或编写新代码时,可以用带有NX参数的SET来替换它。

Syntax
SETNX key value
Available since:
1.0.0
Time complexity:
O(1)
ACL categories:
@write, @string, @fast,

如果key不存在,则将key设置为字符串value。 在这种情况下,它等同于SET。 当key已经持有值时,不执行任何操作。 SETNX是“SET if Not eXists”的缩写。

示例

SETNX mykey "Hello" SETNX mykey "World" GET mykey

设计模式:使用SETNX进行锁定

请注意:

  1. 以下模式不推荐使用,建议采用Redlock算法,虽然实现起来稍微复杂一些,但提供了更好的保证并且具有容错性。
  2. 我们仍然记录了旧模式,因为某些现有的实现将此页面作为参考链接。此外,这是一个有趣的例子,展示了如何使用Redis命令来构建编程原语。
  3. 无论如何,即使假设有一个单实例锁定原语,从2.6.12版本开始,可以使用SET命令来获取锁,并使用一个简单的Lua脚本来释放锁,从而创建一个更简单的锁定原语,相当于这里讨论的那个。这种模式在SET命令页面中有详细记录。

也就是说,SETNX 可以被使用,并且在历史上被用作锁定原语。例如,要获取键 foo 的锁,客户端可以尝试以下操作:

SETNX lock.foo <current Unix time + lock timeout + 1>

如果 SETNX 返回 1,客户端获得了锁,将 lock.foo 键设置为锁不再被视为有效的 Unix 时间。 客户端稍后将使用 DEL lock.foo 来释放锁。

如果 SETNX 返回 0,表示该键已被其他客户端锁定。如果这是一个非阻塞锁,我们可以返回给调用者,或者进入一个循环,不断尝试获取锁,直到成功或某种超时发生。

处理死锁

在上述锁定算法中存在一个问题:如果客户端失败、崩溃或无法释放锁,会发生什么情况? 可以检测到这种情况,因为锁键包含一个UNIX时间戳。 如果这样的时间戳等于当前的Unix时间,锁就不再有效。

当这种情况发生时,我们不能仅仅调用DEL来删除锁,然后尝试发出SETNX,因为这里存在一个竞态条件,当多个客户端检测到一个过期的锁并试图释放它时。

  • C1 和 C2 读取 lock.foo 来检查时间戳,因为它们都在执行 SETNX 后收到了 0,因为锁仍然由 C3 持有,而 C3 在持有锁后崩溃了。
  • C1 发送 DEL lock.foo
  • C1 发送 SETNX lock.foo 并且成功
  • C2 发送 DEL lock.foo
  • C2 发送 SETNX lock.foo 并且成功
  • 错误: 由于竞争条件,C1和C2都获得了锁。

幸运的是,可以使用以下算法避免这个问题。 让我们看看我们的理智客户端C4如何使用这个好算法:

  • C4 发送 SETNX lock.foo 以获取锁

  • 崩溃的客户端 C3 仍然持有它,所以 Redis 会向 C4 回复 0

  • C4 发送 GET lock.foo 来检查锁是否已过期。 如果没有过期,它将休眠一段时间并从头开始重试。

  • 相反,如果锁因为lock.foo的Unix时间比当前Unix时间旧而过期,C4会尝试执行:

    GETSET lock.foo <当前Unix时间戳 + 锁超时时间 + 1>
    
  • 由于GETSET的语义,C4可以检查存储在key中的旧值是否仍然是过期的timestamp。 如果是,则表示锁已被获取。

  • 如果另一个客户端,例如C5,比C4更快并通过GETSET操作获得了锁,C4的GETSET操作将返回一个未过期的时间戳。 C4将简单地从第一步重新开始。 请注意,即使C4将键设置为未来几秒钟,这也不是问题。

为了使这个锁定算法更加健壮,持有锁的客户端在通过DEL解锁键之前应始终检查超时是否未过期,因为客户端故障可能很复杂,不仅仅是崩溃,还可能在某些操作上阻塞很长时间,并在很长时间后尝试发出DEL(当锁已经被另一个客户端持有时)。

RESP2/RESP3 回复

以下之一:


RATE THIS PAGE
Back to top ↑