-
1 # 此岸彼岸君何在
-
2 # 莫里科技研究室
普通實現
說道Redis分散式鎖大部分人都會想到:,或者知道。後一種方式的核心實現命令如下:
這種實現方式有3大要點(也是面試機率非常高的地方):
set命令要用;
value要具有唯一性;
釋放鎖時要驗證value值,不能誤解鎖;
事實上這類瑣最大的缺點就是它加鎖時只作用在一個Redis節點上,即使Redis透過sentinel保證高可用,如果這個master節點由於某些原因發生了主從切換,那麼就會出現鎖丟失的情況:
在Redis的master節點上拿到了鎖;
但是這個加鎖的key還沒有同步到slave節點;
master故障,發生故障轉移,slave節點升級為master節點;
導致鎖丟失。
正因為如此,Redis作者antirez基於分散式環境下提出了一種更高階的分散式鎖的實現方式:Redlock。筆者認為,Redlock也是Redis所有分散式鎖實現方式中唯一能讓面試官高潮的方式。
Redlock實現
antirez提出的redlock演算法大概是這樣的:
在Redis的分散式環境中,我們假設有N個Redis master。這些節點完全互相獨立,不存在主從複製或者其他叢集協調機制。我們確保將在N個例項上使用與在Redis單例項下相同方法獲取和釋放鎖。現在我們假設有5個Redis master節點,同時我們需要在5臺伺服器上面執行這些Redis例項,這樣保證他們不會同時都宕掉。
為了取到鎖,客戶端應該執行以下操作:
獲取當前Unix時間,以毫秒為單位。
依次嘗試從5個例項,使用相同的key和具有唯一性的value(例如UUID)獲取鎖。當向Redis請求獲取鎖時,客戶端應該設定一個網路連線和響應超時時間,這個超時時間應該小於鎖的失效時間。例如你的鎖自動失效時間為10秒,則超時時間應該在5-50毫秒之間。這樣可以避免伺服器端Redis已經掛掉的情況下,客戶端還在死死地等待響應結果。如果伺服器端沒有在規定時間內響應,客戶端應該儘快嘗試去另外一個Redis例項請求獲取鎖。
客戶端使用當前時間減去開始獲取鎖時間(步驟1記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數(N/2+1,這裡是3個節點)的Redis節點都取到鎖,並且使用的時間小於鎖失效時間時,鎖才算獲取成功。
如果取到了鎖,key的真正有效時間等於有效時間減去獲取鎖所使用的時間(步驟3計算的結果)。
如果因為某些原因,獲取鎖失敗(沒有在至少N/2+1個Redis例項取到鎖或者取鎖時間已經超過了有效時間),客戶端應該在所有的Redis例項上進行解鎖(即便某些Redis例項根本就沒有加鎖成功,防止某些節點獲取到鎖但是客戶端沒有得到響應而導致接下來的一段時間不能被重新獲取鎖)。
Redlock原始碼
redisson已經有對redlock演算法封裝,接下來對其用法進行簡單介紹,並對核心原始碼進行分析(假設5個redis例項)。
POM依賴
用法
首先,我們來看一下redission封裝的redlock演算法實現的分散式鎖用法,非常簡單,跟重入鎖(ReentrantLock)有點類似:
唯一ID
實現分散式鎖的一個非常重要的點就是set的value要具有唯一性,redisson的value是怎樣保證value的唯一性呢?答案是UUID+threadId。入口在redissonClient.getLock("REDLOCK_KEY"),原始碼在Redisson.java和RedissonLock.java中:
獲取鎖
獲取鎖的程式碼為redLock.tryLock()或者redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS),兩者的最終核心原始碼都是下面這段程式碼,只不過前者獲取鎖的預設租約時間(leaseTime)是LOCK_EXPIRATION_INTERVAL_SECONDS,即30s:
獲取鎖的命令中,
KEYS[1]就是Collections.singletonList(getName()),表示分散式鎖的key,即REDLOCK_KEY;
ARGV[1]就是internalLockLeaseTime,即鎖的租約時間,預設30s;
ARGV[2]就是getLockName(threadId),是獲取鎖時set的唯一值,即UUID+threadId:
回覆列表
紅鎖(RedLock)是用於分散式網路系統中的一種操作控制機制,即分散式鎖。它解決的問題是在多個主機的系統裡,保證使用者的寫操作的安全性,一致性和高效性。
在分散式網路中,操作的一致性和高效性是矛盾的,為什麼呢?“高效”是指在單位時間裡完成的併發操作越多越好,越快越好;而“一致”是指在網路中某個特定資料在各個主機中的值是相同的,當一個使用者訪問時不會出現在一個主機上是舊值,在另一主機上是新值的情況。為了資料“一致”,在一個使用者更新某個資料時,其他的使用者請求必須等待前面的使用者在全部主機上完成操作後才可以訪問,否則就可能出現訪問結果不一致的情況。這種等待的時間越長,自然系統的效率就越低。如果縮短等待時間,效率會提高,但是有可能上一個使用者還沒有完成全部操作,資料就出現不一致。所以,一致性和高效性就成為一對避不開的矛盾。
好的演算法自然是把這兩項都能提高,就是在保證資料安全的前提下,儘量縮短一個使用者佔用全部主機資源的時間。紅鎖就是一個比較好的解決方案。其原理如下:
假設系統中有 7 臺主機,設一個設鎖的有效時間作為最長允許用時。使用者發出更新請求。
開始計時從第1個到第7個主機挨個加鎖,其中:如果某個主機加鎖的時間超過預定時間(如:50 毫秒),則認為此主機已經不可用,立即放棄並進入下一個主機加鎖。如果在嘗試 7 個主機後,只有 3 個或更少的主機加鎖成功(少於 N/2+1),則認為本次加鎖失敗,將成功加鎖的主機立即去除鎖,返回使用者,報告加鎖失敗。如果全部加鎖完畢後所用的時間小於最初設定的有效時間,並且加鎖的主機數超過一半(4 臺或更多),則認為加鎖成功。反之,則認為加鎖失敗。其他的使用者不定時的發出加鎖請求,一旦請求成功則進入新的加鎖程式。“加鎖”,就是使用者給主機設一個特定的屬性值 Key,同一個使用者的Key 在所有的 7 臺主機是一樣的,其對應的屬性值是隨機產生的值。當 Key 在預定時間內過半數的主機成功設定,則鎖就加上了。如果想解鎖,就將這個 Key 值刪除。使用者想給主機加鎖,要先檢查Key 是否已經存在。如果 Key 已經設了值,而這個值不是這個使用者自己設定的,就放棄加鎖,等待一段時間後再來嘗試,直到 Key 是空值了就可以設定新的 Key 值來加鎖。
紅鎖這樣設定,是保證系統裡一臺或多臺主機宕機了,設鎖的程式仍然可以繼續而不至於導致整個程式夯停。另外每個使用者申請程式的等待時間也是隨機的,可以避免多個使用者在同一時刻申請加鎖導致程式死鎖。這樣系統鎖的排他性就可以保證了。同時,系統處理併發的效率也比較高。