爲了防止分佈式系統中的多個進程之間相互干擾,咱們須要一種分佈式協調技術來對這些進程進行調度。而這個分佈式協調技術的核心就是來實現這個分佈式鎖。node
分佈式鎖有哪些實現redis
add
命令。此命令是原子性操做,只有在 key
不存在的狀況下,才能 add
成功,也就意味着線程獲得了鎖。setnx
命令。此命令一樣是原子性操做,只有在 key
不存在的狀況下,才能 set
成功。setnx(lock_sale_商品ID, 1)
當一個線程實行 setnx
返回 1
,說明 key
本來不存在,該線程成功的到了鎖;當一個線程執行setnx
返回 0
,說明 key
已經存在,該線程搶鎖失敗。算法
當獲得鎖的線程執行完任務,須要釋放鎖,以便其餘線程能夠進入。釋放鎖的最簡單方式是執行 del
指令,僞代碼以下:apache
del(lock_sale_商品ID)
釋放鎖後,其餘線程就能夠繼續執行 setnx
命令來得到鎖。服務器
若是一個獲得鎖的線程在執行任務的過程當中掛掉,來不及顯式地釋放鎖,這塊資源將永遠被鎖住(死鎖),別的線程再也別想進來。因此,setnx
的 key
必須設置一個超時時間,以保證即便沒有被顯式釋放,這把鎖也要在必定時間後自動釋放。setnx
不支持超市參數,因此須要額外的指令,僞代碼以下:網絡
expire(lock_sale_商品ID, 30)
if(setnx(lock_sale_商品ID, 1) == 1){ expire(lock_sale_商品ID, 30) try { do something } finally { del(lock_sale_商品ID) } }
setnx
和 expire
的非原子性setnx
不支持傳入超時時間,但可使用 set
指令,並增長可選參數:數據結構
set(lock_sale_商品ID, 1, 30, NX)
del
致使誤刪能夠在 del
釋放鎖以前作一個判斷,驗證當前的鎖是否是本身加的鎖。具體實現:能夠在加鎖的時候把當前的線程ID當作 value
,並在刪除以前驗證 key
對應的 value
是否是本身線程的ID。架構
加鎖:框架
String threadId = Thread.currentThread().getId() set(key, threadId, 30, NX)
解鎖: 致使新問題:判斷和釋放鎖是兩個獨立操做,不是原子性的。異步
if(threadId.equals(redisClient.get(key))){ del(key) }
對於分佈式鎖,應減小死鎖的可能,並保證操做的原子性
Zookeeper 是一種分佈式協調服務,用於管理大型主機。在分佈式環境中協調和管理服務是一個複雜的過程。Zookeeper 經過極其簡單的架構和 API 解決了這個問題。
Zookeeper 的數據模型很像數據結構中的樹,也很像文件系統的目錄。
樹是由節點組成的,Zookeeper 的數據存儲也是一樣基於節點,這種節點佳做Znode。對於不一樣樹的節點,Znode 的引用方式是路徑引用,相似於文件路徑:/動物/貓
/汽車/寶馬
注意:Zookeeper 是爲讀多寫少的場景所設計。Znode並非用來存儲大規模業務數據,而是用於存儲少許的狀態和配置信息,每一個節點的數據最大不能超過 1MB。
create
delete
exists
getData
setData
getChildren
其中,exests
, getData
, getChildren
屬於讀操做。Zookeeper 客戶端在請求讀操做的時候,能夠選擇是否設置 Watch。
咱們能夠把 Watch 理解成是註冊在特定 Znode 上的觸發器。當這個 Znode 發生改變,也就是調用了 create
, delete
,setData
等方法的時候,將會出發 Znode 上註冊的對應事件,請求 Watch 的客戶端會接受到異步通知。
具體交互過程:
getData
方法,watch
參數是 true
。服務端接收到請求,返回節點數據,而且在對應的哈希表裏插入被 Watch 的 Znode 路徑,以及 Watcher 列表。Zookeeper 身爲分佈式系統協調服務,爲了防止單機掛掉的狀況,Zookeeper 維護了一個集羣。
Zookeeper Service 集羣是一主多從結構。
在更新數據時,首先更新到主節點(這裏的節點指的是服務器,不是 Znode),在同步到從節點。
在讀取數據時,直接讀取任意從節點。
爲了保證主從節點的數據一致性,Zookeeper 採用 ZAB協議,這種協議很是相似於一致性算法那 Paxos 和 Raft。
Zookeeper Atomic Broadcast,有效解決了 Zookeeper 集羣崩潰恢復,以及主從同步數據的問題。
最大 ZXID 也就是節點本地的最新事務編號,包含 epoch 和計數兩部分。epoch是紀元的意思,至關於Raft算法選主時候的 term。
加入 Zookeeper 當前主節點掛掉了,集羣會進行奔潰恢復。ZAB 的奔潰恢復分紅三個階段:
Leader Election
發現階段,用於在從節點中發現最新的 ZXID和事務日誌(爲了防止某些意外的狀況,好比因網絡緣由在上一階段產生了多個Leader的狀況)。
同步階段,把 Leader 剛纔收集獲得的最新歷史事務日誌,同步給集羣中全部的 Follower。只有當半數 Follower 同步成功,這個準 Leader 才能成爲正式的 Leader。
自此,故障恢復正式完成。
ZAB 的數據寫入涉及到 Broadcast 階段,簡單來講,就是 Zookeeper 常規狀況下更新數據的時候,由 Leader 廣播到全部的 Follower。其過程以下:
ZAB 協議既不是強一致性,也不是若一致性,而是處於二者之間的單調一致性(順序一致性)。它依靠事務 ID 和版本號,保證了數據的更新和讀取是有序的。
利用 Zookeeper 的臨時順序節點,能夠輕鬆實現分佈式鎖。
利用 Znode 和 Watcher,能夠實現分佈式服務的註冊和發現。
Redis 的分佈式解決方案 Codis,就利用了 Zookeeper 來存放數據路由表和 codis-proxy 節點的元信息。同時 codis-config 發起的命令都會經過 Zookeeper 同步到各個存活的 codis-proxy。
此外, Kafka、HBase、Hadoop,也都依靠 Zookeeper 同步節點信息,實現高可用。
Zookeeper 的數據結構就像一棵樹,這棵樹由節點組成,這種節點叫作 Znode。
Znode 分爲四種類型:
默認的節點類型。建立節點的客戶端與 Zookeeper 斷開鏈接後,該節點依舊存在。
所謂順序節點,就是在建立節點時,Zookeeper 根據建立的時間順序給該節點名稱進行編號。
與持久節點相反,當建立節點的客戶端與 Zookeeper 斷開鏈接後,臨時節點會被刪除。
臨時順序節點結合 臨時節點和順序節點的特色:在建立節點時,Zookeeper 根據建立的時間順序給該節點名稱進行編號;當建立節點的客戶端與 Zookeeper 斷開鏈接後,臨時節點會被刪除。
釋放鎖的分爲兩種狀況:
當任務完成時,Client1 會顯示調用刪除節點 Lock1 的指令。
得到鎖的 Client1 在執行任務過程當中,若是崩潰,則會斷開與 Zookeeper 服務端的連接。根據臨時節點的特性,相關聯的節點 Lock1 會隨之自動刪除。
這時候Client2 會再次查詢 ParentLock 下面的全部節點,確認本身建立的 Lock2 是否是目前最小的節點。若是是最小,則 Client2 瓜熟蒂落地得到了鎖。
分佈式鎖 | 優勢 | 缺點 |
---|---|---|
Zookeeper | 1. 有封裝好的框架,容易實現。 2. 有等待鎖的隊列,大大提高搶鎖效率。 |
添加和刪除節點性能低。 |
Redis | Set 和 Del 指令的性能較高。 | 1. 實現複雜,須要考慮超時、原子性、誤刪等情形。 2. 沒有等待鎖的隊列,只能在客戶端自旋來等鎖,效率低下。 |