SETNX (已弃用)
从 Redis 版本 2.6.12 开始,此命令被视为已弃用。
它可以替换为SET
使用NX
参数。
SETNX key value
- 从以下位置开始可用:
- 1.0.0
- 时间复杂度:
- O(1)
- ACL 类别:
-
@write
,@string
,@fast
,
设置key
保持字符串value
如果key
不存在。
在这种情况下,它等于SET
.
什么时候key
already hold a 值,则不执行任何作。SETNX
是 “SET if Not eXists” 的缩写。
例子
设计模式:锁定方式SETNX
请注意:
- 不建议使用以下模式,而使用 Redlock 算法,该算法的实现稍微复杂一些,但提供了更好的保证并且具有容错能力。
- 无论如何,我们都会记录旧模式,因为某些现有的实现会链接到此页面作为参考。此外,这是一个有趣的例子,说明如何使用 Redis 命令来挂载编程原语。
- 无论如何,即使假设一个单实例锁定原语,从 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
密钥已被其他客户端锁定。
如果是非阻塞锁,我们可以返回给调用者,也可以进入循环
重试持有锁,直到我们成功或某种超时过期。
处理死锁
在上面的锁定算法中,有一个问题:如果客户端 失败、崩溃或无法释放锁? 之所以能够检测到这种情况,是因为 lock key 包含 UNIX 时间戳。 如果这样的时间戳等于当前 Unix 时间,则锁不再是 有效。
当这种情况发生时,我们不能只调用DEL
对着钥匙去锁
,然后尝试发出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 都获取了锁。
幸运的是,使用以下算法可以避免此问题。 让我们看看我们的 sane 客户端 C4 如何使用良好的算法:
-
C4 发送
SETNX lock.foo
为了获取锁 -
崩溃的客户端 C3 仍然持有它,因此 Redis 将回复
0
到 C4。 -
C4 发送
GET lock.foo
检查锁是否过期。 如果不是,它将休眠一段时间,然后从头开始重试。 -
相反,如果锁已过期,因为 Unix 时间在
lock.foo
年龄较大 C4 尝试执行:GETSET lock.foo <current Unix timestamp + lock timeout + 1>
-
由于
GETSET
semantic 中,C4 可以检查存储在key
仍为过期时间戳。 如果是,则已获取锁。 -
如果另一个客户端(例如 C5)比 C4 更快并获取了锁 使用
GETSET
作, C4GETSET
作将返回非 expired 时间戳。 C4 将简单地从第一步重新启动。 请注意,即使 C4 将密钥设置在将来几秒钟后,这也是 没问题。
为了使此锁定算法更加健壮,
持有锁的客户端应始终检查 timeout didn't expiration before
解锁密钥DEL
因为客户端故障可能很复杂,而不仅仅是
崩溃但也阻止了大量时间来对抗某些作和尝试
发行DEL
经过大量时间后(当 LOCK 已由另一个人持有时
client) 的
RESP2/RESP3 回复
以下选项之一: