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