前言
整理了一下自己日常中使用到的一些分佈式鎖功能,特別說明,因爲還在整理知識點,有些東西我直接使用之前寫的PythonDemo一筆蓋過,望體諒,最近有點忙。
分佈式鎖
分佈式鎖是通過互訴等手段,來實現分佈式系統之間同步訪問共享資源,以保證數據的一致性。
一般實現分佈式鎖都有哪種方式
方法 | 優勢 | 優劣對比 | 備註 |
---|---|---|---|
Redis | 性能高 | 可靠性低,爭議多 | 不推薦,但業務不需要太嚴謹時可以考慮 |
ZooKeeper | 性能一般 | 可靠性高 | 推薦,但性能未必出色 |
鎖衝突情況
-
Redis:如果沒有獲取到這把鎖,只能自己每個一秒嘗試一下,看能否拿到這把鎖,開銷大
-
ZooKeeper:如果沒有獲取到這把鎖,對這個鎖註冊監聽器,被釋放時通知節點掛掉的情況,開銷小
異常情況
- Redis非redis-cluster模式下,如單節點,哨兵Sentinel,節點掛掉後,需要通過過期時間自動釋放
- zk的話,因爲創建的是臨時znode,只要客戶端掛了,znode就沒了,此時就自動釋放鎖
Redis分佈鎖
官方叫做RedLock算法,是redis官方支持的分佈式鎖算法。
Redis 2.8 版本中作者加入了 set 指令的擴展參數,使得 setnx 和 expire 指令可以一起執行
三個重要的考量點
- 互斥(只能有一個客戶端獲取鎖)
- 不能死鎖
- 容錯(大部分redis節點或者這個鎖就可以加可以釋放)
爲什麼key要用隨機值?
因爲如果某個客戶端獲取到了鎖,但是阻塞了很長時間才執行完,此時可能已經自動釋放鎖了,此時可能別的客戶端已經獲取到了這個鎖,要是你這個時候直接刪除key的話會有問題,所以得用隨機值加上面的lua腳本來釋放鎖。
最普通的實現方式,如果就是在redis裏創建一個key算加鎖
參數 | 說明 |
---|---|
nx | 只有key不存在的時候才能設置成功 |
px | 多少秒後自動釋放 |
set mykey 隨機值 nx px 5
釋放鎖就是刪除key,但是一般可以用lua腳本刪除,判斷value一樣才刪除
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
風險
- 如果是普通的redis單實例,那就是單點故障。
- 比如在 Sentinel 集羣中,主節點掛掉時,從節點會取而代之,客戶端上卻並沒有明顯感知。從節點還沒來得及同步過來,主節點就掛掉了,這樣就會導致系統中同樣一把鎖被兩個客戶端同時持有,不安全性由此產生。
使用RedLock算法
針對以上問題,可以使用redis cluster,假設有5個redis master實例。然後執行如下步驟獲取一把鎖:
1)獲取當前時間戳,單位是毫秒
2)跟上面類似,輪流嘗試在每個master節點上創建鎖,過期時間較短,一般就幾十毫秒
3)嘗試在大多數節點上建立一個鎖,比如5個節點就要求是3個節點(n / 2 +1)
4)客戶端計算建立好鎖的時間,如果建立鎖的時間小於超時時間,就算建立成功了
5)要是鎖建立失敗了,那麼就依次刪除這個鎖
6)只要別人建立了一把分佈式鎖,你就得不斷輪詢去嘗試獲取鎖
討論
其實在正常工作中,我們不太推薦這個鎖,而且這個官方發佈的RedLock算法有很多爭議,國外不乏大牛質疑這個做法,我找到了兩篇文章,我覺得寫的很好
部署
ZooKeeper實現分佈式鎖
**鎖定義:**Zookeeper通過創建臨時數據節點來表示一個鎖,並且是順序節點
**獲取鎖:**成功創建數據節點的客戶端即爲獲取鎖。未獲取鎖的客戶端通過監聽該節點變更情況,以便重新獲取鎖
釋放鎖:
- 當前客戶端發生宕機的情況下,臨時節點自動刪除
- 正常業務邏輯處理完畢後,客戶端自動刪除自己創建的臨時節點
簡單示例
以下代碼只是簡單實現,基於zookeeper的臨時順序節點去實現的,不建議這個,下面有高級用法
Curator - 實現分佈式鎖
它的實現也是通過創建臨時的順序節點機制
類型 | 說明 |
---|---|
InterProcessMutex | 互斥鎖 |
zk客戶端登錄,創建節點
create /orderNo 1000
代碼實現,輕鬆實現分佈式鎖
// 獲得鎖
interProcessMutex.acquire();
// 生成訂單
int order = generateOrder(orderNoPath, curatorFramework);
System.out.println("當前訂單號:" + order);
// 釋放
interProcessMutex.release();