?
This document uses PHP Chinese website manual Release
SETNX key value
自1.0.0起可用。
時間復(fù)雜度: O(1)
如果不存在,則設(shè)置key
為保存字符串。在這種情況下,它等于SET。當已經(jīng)保存一個值時,不執(zhí)行任何操作。SETNX 是短期的“ SET如果? OT? X派”。valuekeykey
整數(shù)回復(fù),具體為:
1
如果密鑰已設(shè)置
0
如果密鑰沒有設(shè)置
redis> SETNX mykey "Hello" (integer) 1
redis> SETNX mykey "World" (integer) 0
redis> GET mykey "Hello"
SETNX
請注意:
以下模式不利于Redlock算法,該算法實現(xiàn)起來稍微復(fù)雜一些,但提供了更好的保證并具有容錯能力。
無論如何,我們記錄舊的模式,因為某些現(xiàn)有的實現(xiàn)鏈接到此頁面作為參考。此外,Redis 命令如何用于裝載編程原語是一個有趣的例子。
無論如何,即使假定一個單實例鎖定原語,從2.6.12開始,可以創(chuàng)建一個更簡單的鎖定原語,與此處討論的等價,使用 SET 命令獲取鎖,以及一個簡單的 Lua 腳本來釋放鎖。該模式記錄在SET命令頁面中。
也就是說,SETNX 可以被用作歷史上被用作鎖定原語。例如,要獲取密鑰的鎖定foo
,客戶端可以嘗試以下操作:
SETNX lock.foo <current Unix time + lock timeout + 1>
如果 SETNX 返回1
客戶端獲得的鎖定,則將該lock.foo
關(guān)鍵字設(shè)置為不再認為該鎖定有效的 Unix 時間??蛻舳松院髮⑹褂?code>DEL lock.foo以釋放鎖。
如果 SETNX 返回0
該鍵已被某個其他客戶端鎖定。如果它是非阻塞鎖,我們可以返回給調(diào)用者,或者輸入一個循環(huán)重試以保持鎖,直到我們成功或某種超時過期。
在上述鎖定算法中存在一個問題:如果客戶端出現(xiàn)故障,崩潰或無法釋放鎖,會發(fā)生什么情況?可以檢測到這種情況,因為鎖定鍵包含 UNIX 時間戳。如果這樣的時間戳等于當前的 Unix 時間,則該鎖不再有效。
發(fā)生這種情況時,我們不能僅僅通過調(diào)用 DEL 來移除鎖,然后嘗試發(fā)出 SETNX,因為這里存在爭用條件,當多個客戶端檢測到過期的鎖并嘗試釋放它時。
C1和C2讀取lock.foo
以檢查時間戳,因為它們0
在執(zhí)行SETNX 之后都收到,因為鎖仍然由持有鎖之后崩潰的 C3 持有。
C1發(fā)送 DEL lock.foo
C1發(fā)送SETNX lock.foo
并成功
C2發(fā)送 DEL lock.foo
C2發(fā)送SETNX lock.foo
并成功
錯誤:由于競爭條件,C1和C2都獲得了鎖定。
幸運的是,使用以下算法可以避免此問題。讓我們看看我們的理智客戶C4如何使用好的算法:
C4發(fā)送SETNX lock.foo
以獲取鎖
崩潰的客戶端C3仍然擁有它,所以Redis將回復(fù)0
給C4。
C4發(fā)送GET lock.foo
來檢查鎖是否過期。如果不是,它會睡眠一段時間并從一開始就重試。
相反,如果鎖由于 Unix 時間lock.foo
比當前 Unix 時間早而過期,C4會嘗試執(zhí)行:
GETSET lock.foo <current Unix timestamp + lock timeout + 1>
由于 GETSET 語義,C4可以檢查存儲的舊值key
是否仍然是過期的時間戳。如果是這樣,那就得到了鎖。
如果另一個客戶端(例如C5)比C4速度更快并通過 GETSET 操作獲取了鎖定,則C4 GETSET 操作將返回一個未過期的時間戳。C4將從第一步重新開始。請注意,即使C4在未來幾秒鐘內(nèi)將鍵設(shè)置為幾秒,這也不是問題。
為了使這種鎖定算法更加健壯,持有鎖的客戶端應(yīng)該始終檢查超時在 DEL 用于解鎖密鑰之前沒有過期,因為客戶端失敗可能很復(fù)雜,不僅會崩潰,而且會阻止大量時間對付某些操作并嘗試在很長時間后發(fā)出 DEL(當 LOCK 已被另一客戶端占用時)。