# SETNX

仅当key不存在时,才设置key的值

语法

SETNX key value
  • 可用版本:

    1.0.0

  • 时间复杂度:

    O(1)

  • ACL 类别:

    @write, @string, @fast

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

# 返回

整数,具体来说:

  • 1如果设置了密钥
  • 0如果未设置密钥

# 例子

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis> 

# 设计模式:锁定与SETNX

请注意:

  1. 不鼓励使用以下模式以支持 Redlock 算法,该算法 实现起来稍微复杂一些,但提供了更好的保证并且具有容错性。
  2. 无论如何,我们都会记录旧模式,因为某些现有实现链接到此页面作为参考。此外,这是一个有趣的例子,说明了如何使用 Redis 命令来挂载编程原语。
  3. 无论如何,即使假设一个单实例锁定原语,从 2.6.12 开始,也可以创建一个更简单的锁定原语,相当于这里讨论的那个,使用 SET 获取锁的命令和一个简单的 Lua 脚本来释放锁. 该模式记录在 SET 命令页面中。

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

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

如果SETNX返回1客户端获取锁,则将lock.foo密钥设置为不再认为锁有效的 Unix 时间。客户端稍后将使用DEL lock.foo以释放锁。

如果SETNX返回0,则密钥已被其他客户端锁定。如果它是一个非阻塞锁,我们可以返回给调用者,或者进入一个循环重试持有锁,直到我们成功或某种超时到期。

# 处理死锁

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

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

  • C1 和 C2 读取lock.foo以检查时间戳,因为它们都是 0在执行后收到的SETNX,因为锁仍然由持有锁后崩溃的 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 将回复0C4。

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

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

    GETSET lock.foo <current Unix timestamp + lock timeout + 1>
    
  • 由于 GETSET 语义,C4 可以检查存储的旧值 key是否仍然是过期的时间戳。如果是,则获得了锁。

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

为了使这种锁定算法更加健壮,持有锁的客户端在解锁密钥之前应该始终检查超时没有过期, DEL 因为客户端故障可能很复杂,不仅崩溃而且还会阻塞大量时间来执行某些操作和尝试 DEL 在很长一段时间后发出(当 LOCK 已被另一个客户端持有时)。

# 反馈

如果您在此页面上发现问题,或有改进建议,请提交请求以合并或打开存储库中的问题。

Last Updated: 4/18/2023, 8:45:33 AM