您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“Redis并發(fā)問(wèn)題”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
Redis為單進(jìn)程單線程模式,采用隊(duì)列模式將并發(fā)訪問(wèn)變?yōu)榇性L問(wèn)。Redis本身沒(méi)有鎖的概念,Redis對(duì)于多個(gè)客戶端連接并不存在競(jìng)爭(zhēng),但是在Jedis客戶端對(duì)Redis進(jìn)行并發(fā)訪問(wèn)時(shí)會(huì)發(fā)生連接超時(shí)、數(shù)據(jù)轉(zhuǎn)換錯(cuò)誤、阻塞、客戶端關(guān)閉連接等問(wèn)題,這些問(wèn)題均是由于客戶端連接混亂造成。對(duì)此有2種解決方法:
1.客戶端角度,為保證每個(gè)客戶端間正常有序與Redis進(jìn)行通信,對(duì)連接進(jìn)行池化,同時(shí)對(duì)客戶端讀寫Redis操作采用內(nèi)部鎖synchronized。
2.服務(wù)器角度,利用setnx實(shí)現(xiàn)鎖。
對(duì)于第一種,需要應(yīng)用程序自己處理資源的同步,可以使用的方法比較通俗,可以使用synchronized也可以使用lock;第二種需要用到Redis的setnx命令,但是需要注意一些問(wèn)題。
SETNX命令(SET if Not eXists)
語(yǔ)法:
SETNX key value
功能:
將 key 的值設(shè)為 value ,當(dāng)且僅當(dāng) key 不存在;若給定的 key 已經(jīng)存在,則 SETNX 不做任何動(dòng)作。
時(shí)間復(fù)雜度:
O(1)
返回值:
設(shè)置成功,返回 1 。
設(shè)置失敗,返回 0 。
模式:將 SETNX 用于加鎖(locking)
SETNX 可以用作加鎖原語(yǔ)(locking primitive)。比如說(shuō),要對(duì)關(guān)鍵字(key) foo 加鎖,客戶端可以嘗試以下方式:
SETNX lock.foo <current Unix time + lock timeout + 1>
如果 SETNX 返回 1 ,說(shuō)明客戶端已經(jīng)獲得了鎖, key 設(shè)置的unix時(shí)間則指定了鎖失效的時(shí)間。之后客戶端可以通過(guò) DEL lock.foo 來(lái)釋放鎖。
如果 SETNX 返回 0 ,說(shuō)明 key 已經(jīng)被其他客戶端上鎖了。如果鎖是非阻塞(non blocking lock)的,我們可以選擇返回調(diào)用,或者進(jìn)入一個(gè)重試循環(huán),直到成功獲得鎖或重試超時(shí)(timeout)。
但是已經(jīng)證實(shí)僅僅使用SETNX加鎖帶有競(jìng)爭(zhēng)條件,在特定的情況下會(huì)造成錯(cuò)誤。
處理死鎖(deadlock)
上面的鎖算法有一個(gè)問(wèn)題:如果因?yàn)榭蛻舳耸?、崩潰或其他原因?qū)е聸](méi)有辦法釋放鎖的話,怎么辦?
這種狀況可以通過(guò)檢測(cè)發(fā)現(xiàn)——因?yàn)樯湘i的 key 保存的是 unix 時(shí)間戳,假如 key 值的時(shí)間戳小于當(dāng)前的時(shí)間戳,表示鎖已經(jīng)不再有效。
但是,當(dāng)有多個(gè)客戶端同時(shí)檢測(cè)一個(gè)鎖是否過(guò)期并嘗試釋放它的時(shí)候,我們不能簡(jiǎn)單粗暴地刪除死鎖的 key ,再用 SETNX 上鎖,因?yàn)檫@時(shí)競(jìng)爭(zhēng)條件(race condition)已經(jīng)形成了:
C1 和 C2 讀取 lock.foo 并檢查時(shí)間戳, SETNX 都返回 0 ,因?yàn)樗呀?jīng)被 C3 鎖上了,但 C3 在上鎖之后就崩潰(crashed)了。
C1 向 lock.foo 發(fā)送 DEL 命令。
C1 向 lock.foo 發(fā)送 SETNX 并成功。
C2 向 lock.foo 發(fā)送 DEL 命令。
C2 向 lock.foo 發(fā)送 SETNX 并成功。
出錯(cuò):因?yàn)楦?jìng)爭(zhēng)條件的關(guān)系,C1 和 C2 兩個(gè)都獲得了鎖。
幸好,以下算法可以避免以上問(wèn)題。來(lái)看看我們聰明的 C4 客戶端怎么辦:
C4 向 lock.foo 發(fā)送 SETNX 命令。
因?yàn)楸罎⒌舻?C3 還鎖著 lock.foo ,所以 Redis 向 C4 返回 0 。
C4 向 lock.foo 發(fā)送 GET 命令,查看 lock.foo 的鎖是否過(guò)期。如果不,則休眠(sleep)一段時(shí)間,并在之后重試。
另一方面,如果 lock.foo 內(nèi)的 unix 時(shí)間戳比當(dāng)前時(shí)間戳老,C4 執(zhí)行以下命令:
GETSET lock.foo <current Unix timestamp + lock timeout + 1>
因?yàn)?GETSET 的作用,C4 可以檢查看 GETSET 的返回值,確定 lock.foo 之前儲(chǔ)存的舊值仍是那個(gè)過(guò)期時(shí)間戳,如果是的話,那么 C4 獲得鎖。
如果其他客戶端,比如 C5,比 C4 更快地執(zhí)行了 GETSET 操作并獲得鎖,那么 C4 的 GETSET 操作返回的就是一個(gè)未過(guò)期的時(shí)間戳(C5 設(shè)置的時(shí)間戳)。C4 只好從第一步開始重試。
注意,即便 C4 的 GETSET 操作對(duì) key 進(jìn)行了修改,這對(duì)未來(lái)也沒(méi)什么影響。
這里假設(shè)鎖key對(duì)應(yīng)的value沒(méi)有實(shí)際業(yè)務(wù)意義,否則會(huì)有問(wèn)題,而且其實(shí)其value也確實(shí)不應(yīng)該用在業(yè)務(wù)中。
為了讓這個(gè)加鎖算法更健壯,獲得鎖的客戶端應(yīng)該常常檢查過(guò)期時(shí)間以免鎖因諸如 DEL 等命令的執(zhí)行而被意外解開,因?yàn)榭蛻舳耸〉那闆r非常復(fù)雜,不僅僅是崩潰這么簡(jiǎn)單,還可能是客戶端因?yàn)槟承┎僮鞅蛔枞讼喈?dāng)長(zhǎng)時(shí)間,緊接著 DEL 命令被嘗試執(zhí)行(但這時(shí)鎖卻在另外的客戶端手上)。
GETSET命令
語(yǔ)法:
GETSET key value
功能:
將給定 key 的值設(shè)為 value ,并返回 key 的舊值(old value)。當(dāng) key 存在但不是字符串類型時(shí),返回一個(gè)錯(cuò)誤。
時(shí)間復(fù)雜度:
O(1)
返回值:
返回給定 key 的舊值;當(dāng) key 沒(méi)有舊值時(shí),也即是, key 不存在時(shí),返回 nil 。
“Redis并發(fā)問(wèn)題”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。