SETNX

仅当某个键不存在时才设置该键的字符串值。

语法

SETNX key value

引入版本:1.0.0

复杂度摘要:O(1)

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

必需参数

key

键名称。

value

仅当键尚不存在时才设置的值。

示例

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

详细说明

设计模式:用SETNX锁定

请注意:

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

That said, SETNX can be used, and was historically used, as a locking primitive. For example, to acquire the lock of the key foo, the client could try the following:

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持有。
  • C1发送DEL lock.foo
  • C1发送SETNX lock.foo成功
  • C2发送DEL lock.foo
  • C2发送SETNX lock.foo成功
  • ERROR:由于竞争条件,C1 和 C2 都获得了锁。

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

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

  • 崩溃的客户端C3仍然持有它,因此Redis将回复0给C4。

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

  • 相反,如果由于 lock.foo 处的 Unix 时间早于当前 Unix 时间而导致锁定过期,则 C4 会尝试执行:

    GETSET lock.foo <current Unix timestamp + lock timeout + 1>

  • 由于 GETSET 语义,C4 可以检查存储在 key 中的旧值是否仍然是过期时间戳。如果是,则已获取锁。

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

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

Redis Software 与 Redis Cloud 兼容性

Redis
软件
Redis
Cloud
说明
✅ 标准
✅ 主动-主动
✅ 标准
✅ 主动-主动

返回值

RESP2

以下之一: * 整数回复:0(如果未设置键)。 * 整数回复:如果设置了键,则为 1。

RESP3

以下之一: * 整数回复:0(如果未设置键)。 * 整数回复:如果设置了键,则为 1。

资料来源:Redis 官方 SETNX 文档;中文内容参考 Redis 文档许可。