業務上的用到的redis分佈式鎖是在多個ap操作同一個數據庫表上,以此來避免表鎖。
多隻AP 去搶鎖,搶到鎖之後纔可以對數據做一系列的增刪改的操作。首先都會搶着去佔鎖,搶到後直接設定過期時間,如果獲取到的value是nil值則直接刪除該key。等待下一次搶鎖。
關於鎖的問題也出過幾次問題:
1、程序異常中斷,鎖未釋放,導致AP 在空跑,設定的鎖過期時間是15mins
關鍵字: setnx 『SET if Not eXists』(如果不存在,則 SET)
可以看到這個鎖被t1 所在的服務器搶到後,返回1.這時t2所在的服務器去拿看到已經被t1拿到了,此時就不能對錶做任何操作,理論上來說T1所在的服務器 操作完後應該del掉這個key。此時程序執行到del key那行代碼之前異常中斷了。
那後續的刪除key操作就沒法執行,這個key會一直在Redis中存在,其他服務器每次去檢查,都會返回0,他們都會認爲有人在使用鎖,我需要等。還好設定了過期時間爲15mins,但是這也會出現數據延遲的。
127.0.0.1:19000> setnx HMS:LOCK:table t1
(integer) 1
127.0.0.1:19000> get HMS:LOCK:table
"t1"
127.0.0.1:19000> setnx HMS:LOCK:table t2
(integer) 0
127.0.0.1:19000>
2、如果沒有設定過期時間。釋放鎖的操作僅僅交給AP
通過value值來判斷是否可以刪除key,比如服務器1,設置了value也就是timeout爲當前時間+1秒 ,這個時候服務器2通過get發現時間已經超過系統當前時間了,那就說明服務器1沒有釋放鎖,服務器1可能出問題了,服務器2就開始執行刪除key操作,並且繼續執行setnx操作。
但是這塊有一個問題,也就是不光你服務器2可能會發現服務器1超時了,服務器3也可能會發現,如果剛好服務器2 setnx操作完成,服務器3就接着刪除,是不是服務器3也可以setnx成功了?
服務器2和3同時拿到了鎖?這就太不安全了
這時候需要使用GETSET key value這個操作,這個操作的意思就是 返回上一次的值並將key置爲現在的value。
127.0.0.1:19000> set HMS:LOCK:table t1
OK
127.0.0.1:19000> get HMS:LOCK:table
"t1"
127.0.0.1:19000> getset HMS:LOCK:table t2 #返回舊值 t1
"t1"
127.0.0.1:19000>
所以服務器2發現鎖過期了不能直接del 然後setnx。 應該getset 然後用獲取的時間判斷是否過期,如果獲取的時間仍然是過期的,那就說明拿到鎖了。
如果沒有,則說明在服務2執行getset之前,服務器3可能也發現鎖過期了,並且在服務器2之前執行了getset操作,重新設置了過期時間。
那麼服務器2就需要放棄後續的操作,繼續等待服務器3釋放鎖或者去監測key的有效期是否過期。
GETSET 可以和 INCR 組合使用,實現一個有原子性(atomic)復位操作的計數器(counter)。
舉例來說,每次當某個事件發生時,進程可能對一個名爲 mycount 的 key 調用 INCR 操作,通常我們還要在一個原子時間內同時完成獲得計數器的值和將計數器值復位爲 0 兩個操作。
127.0.0.1:19000> set count 99
OK
127.0.0.1:19000> incr count
(integer) 100
127.0.0.1:19000> getset count 0
"100"
127.0.0.1:19000> get count
"0"
這種操作對目前的業務來說是使用了雙保險過期+刪除的操作了避免死鎖。
zk中鎖是通過文件系統實現的,zk通過創建Znode,其他服務器通過檢測zk的節點的狀態來獲取鎖,zk最重要的機制就是半數機制,因此安裝zk的服務器個數應該是奇數個,zk如果發現某個節點掛掉會自動剔除,如果是leader 掛掉會重新選舉新的leader。