setnx
redis 分布式鎖使用非常廣泛的,來實(shí)現(xiàn)對一些共享資源進(jìn)行互斥訪問。
一般使用setnx(set if not exists) 來搶占,del 來釋放。
setnx lock 1
... do something ...
del lock
但是這個(gè)流程有問題,如果del 調(diào)用失敗或者異常導(dǎo)致del 沒有調(diào)用,就會(huì)陷入死鎖,導(dǎo)致鎖永遠(yuǎn)不能釋放??梢韵氲降囊粋€(gè)解決方案就是我們給鎖加一個(gè)過期時(shí)間,如下:
setnx lock 1
expire lock 5
... do something ...
del lock
但是上述流程還是有問題,如果expire 調(diào)用失敗,還是會(huì)陷入死鎖。于是我們想到下面代碼程序校驗(yàn)的方案來解決可能的死鎖問題:
current_ts = time()
lock_ts = redis->get('lock')
if (!lock_ts || current_ts - lock_ts > 5) {
redis->set(lock, current_ts)
redis->expire(lock, 5)
... do something ...
del lock
} else {
return false
}
但是上述方案還是有問題的,原因就在于搶占資源不是原子操作的,當(dāng)我們的程序訪問并發(fā)比較高的時(shí)候,會(huì)有多個(gè)訪問同時(shí)搶占到鎖,不能保證互斥性。
那怎么解決呢? 好在Redis 2.8 版本之后,redis 的set 執(zhí)行增加了擴(kuò)展參數(shù),使setnx 和expire 可以一起執(zhí)行,從而解決了上述難題。
SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
set lock 1 ex 5 nx
... do something ...
del lock
del誤刪
上述方案還是會(huì)存在誤刪問題:假如線程A獲得鎖并且設(shè)置超時(shí)時(shí)間為30秒,某種原因?qū)е戮€程A執(zhí)行很慢,超過30秒后線程A鎖自動(dòng)過期,釋放了鎖,線程B獲了鎖。隨后線程A執(zhí)行完成,del刪除鎖,但線程B還未執(zhí)行完成,實(shí)際上線程A刪除的是線程B的鎖。
解決方案就是通過lua腳本實(shí)現(xiàn)一個(gè)樂觀鎖:
線程在加鎖的時(shí)候,可以給鎖加一個(gè)版本號或者隨機(jī)數(shù)來區(qū)分。
SET lock rand_value EX 300 NX
del鎖之前先做判斷,通過lua腳本來保證判斷和del 兩個(gè)操作的原子性。
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
其他問題
前面這個(gè)算法中出現(xiàn)的鎖的有效時(shí)間(lock validity time),設(shè)置成多少合適呢?如果設(shè)置太短的話,鎖就有可能在客戶端完成對于共享資源的訪問之前過期,從而失去保護(hù);如果設(shè)置太長的話,一旦某個(gè)持有鎖的客戶端釋放鎖失敗,那么就會(huì)導(dǎo)致所有其它客戶端都無法獲取鎖,從而長時(shí)間內(nèi)無法正常工作??磥碚媸莻€(gè)兩難的問題。
假如Redis節(jié)點(diǎn)宕機(jī)了,那么所有客戶端就都無法獲得鎖了,服務(wù)變得不可用。為了提高可用性,我們可以給這個(gè)Redis節(jié)點(diǎn)掛一個(gè)Slave,當(dāng)Master節(jié)點(diǎn)不可用的時(shí)候,系統(tǒng)自動(dòng)切到Slave上(failover)。但由于Redis的主從復(fù)制(replication)是異步的,這可能導(dǎo)致在failover過程中喪失鎖的安全性。這是基于單Redis節(jié)點(diǎn)的分布式鎖無法解決的,而Redlock算法就是為了解決這個(gè)問題提出的,是基于多個(gè)Redis節(jié)點(diǎn)(都是Master)的一種實(shí)現(xiàn)。詳情可以看下官網(wǎng)介紹:https://redis.io/topics/distlock ,也可以參考一下這篇blog的介紹:http://zhangtielei.com/posts/blog-redlock-reasoning.html