要保證redis操作的原子性,一般來說有兩種方式,一種是使用redis的單命令操作,另一種則是使用lua腳本保證原子性。
結(jié)合分布式鎖,我們一般使用SETNX命令來完成加鎖,DEL命令來完成鎖的釋放,雖然setnx是原子性的,但是setnx+del
并不是原子性的,所以其中便產(chǎn)生風(fēng)險(xiǎn)點(diǎn)
風(fēng)險(xiǎn)1:成功加鎖之后,由于業(yè)務(wù)異常導(dǎo)致鎖并沒有釋放,這導(dǎo)致其他客戶點(diǎn)無法獲取到鎖,在redis層面,我們應(yīng)當(dāng)
通過增加鎖的過期時(shí)間。
風(fēng)險(xiǎn)2:就是redis并區(qū)分不了客戶端的操作是否是來自同一客戶端,舉個(gè)例子,A執(zhí)行setnx,B執(zhí)行del,C執(zhí)行setnx,
這就會(huì)造成A和C都獲取到了鎖。
解決方案:redis給set命令實(shí)現(xiàn)了類似SETNX的功能,針對(duì)上述的問題,SET lock_key unique_value NX PX 10000,
我們通過開啟set命令的NX和PX選項(xiàng),NX起到不存在即設(shè)置的效果,PX則是毫秒級(jí)的過期時(shí)間,然后我們通過對(duì)value
設(shè)置特殊標(biāo)識(shí),以便于DEL操作的時(shí)候,判斷是否是加鎖的客戶端刪除,而不是別的客戶端導(dǎo)致的誤刪。
在執(zhí)行DEL的時(shí)候,需要讀取鎖變量的值,判斷是否等于執(zhí)行DEL操作的客戶端的標(biāo)識(shí),一致才滿足才執(zhí)行刪除,所以一般DEL操作
通過lua腳本完成以保證其原子性。然后問題來了,如果我們的redis實(shí)例宕機(jī)了,鎖的可靠性得不到保證,怎么辦?
隨著redis鎖的可靠性的問題的產(chǎn)生,分布式鎖算法redlock應(yīng)運(yùn)而生,redlock的思路是客戶端和多個(gè)redis實(shí)例請求加鎖,
如果能和半數(shù)以上的實(shí)例加鎖成功,則認(rèn)為獲得分布式鎖。這樣做可以保證在有redis實(shí)例宕機(jī)的情況下,其他實(shí)例也有鎖變量,
不會(huì)影響客戶端的正常進(jìn)行。具體操作如下
使用 SET 命令,帶上 NX,EX/PX 選項(xiàng),以及值設(shè)置為客戶端的唯一標(biāo)識(shí),為了防止加鎖的時(shí)候?qū)嵗礄C(jī),需要設(shè)置一個(gè)
加鎖超時(shí)時(shí)間,超時(shí)便跟下一個(gè)實(shí)例執(zhí)行加鎖操作,要注意的是加鎖的超時(shí)時(shí)間,需要遠(yuǎn)小于鎖的有效時(shí)間,不然等加完鎖
留給業(yè)務(wù)邏輯執(zhí)行的時(shí)間就不多了。
怎么算加鎖成功呢?需要和超過半數(shù)的實(shí)例完成加鎖,加鎖的總耗時(shí)小于鎖的有效時(shí)長,然后計(jì)算鎖的剩余有效時(shí)間,
如果來不及完成業(yè)務(wù)共享數(shù)據(jù)的操作,那么便釋放鎖。
總結(jié):使用分布式鎖,需要注意操作的原子性,以及異常情況的導(dǎo)致鎖無法釋放情況,通過設(shè)置超時(shí)時(shí)間解決,
釋放鎖的時(shí)候,需要設(shè)置客戶端唯一標(biāo)識(shí)以避免出現(xiàn)誤刪情況,使用lua腳本保證DEL操作的原子性,
通過redlock,實(shí)現(xiàn)高可用分布式鎖。