Zookeep 分佈式鎖

什麼是分佈式鎖

概述

爲了防止分佈式系統中的多個進程之間相互干擾,咱們須要一種分佈式協調技術來對這些進程進行調度。而這個分佈式協調技術的核心就是來實現這個分佈式鎖node

分佈式鎖應具有的條件

  • 在分佈式系統環境下,一個方法在同一時間只能被一個機器的一個線程執行
  • 高可用的獲取鎖與釋放鎖
  • 高性能的獲取鎖與釋放鎖
  • 具有可重入特性
  • 具有鎖失效機制
  • 具有非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗

分佈式鎖有哪些實現redis

  • Memcached:利用 Memcached 的 add 命令。此命令是原子性操做,只有在 key 不存在的狀況下,才能 add 成功,也就意味着線程獲得了鎖。
  • Redis:利用 setnx 命令。此命令一樣是原子性操做,只有在 key 不存在的狀況下,才能 set 成功。
  • Zookeeper:利用 Zookeeper 的順序臨時節點,來實現分佈式鎖和等待隊列。Zookeeper 設計的初衷,就是爲了實現分佈式鎖服務的。
  • Chubby:Google 公司實現的粗粒度分佈式鎖服務,底層利用了 Paxos 一致性算法。

Redis分佈式鎖的實現

加鎖

setnx(lock_sale_商品ID, 1)

當一個線程實行 setnx 返回 1,說明 key 本來不存在,該線程成功的到了鎖;當一個線程執行setnx 返回 0,說明 key 已經存在,該線程搶鎖失敗。算法

解鎖

當獲得鎖的線程執行完任務,須要釋放鎖,以便其餘線程能夠進入。釋放鎖的最簡單方式是執行 del 指令,僞代碼以下:apache

del(lock_sale_商品ID)

釋放鎖後,其餘線程就能夠繼續執行 setnx 命令來得到鎖。服務器

鎖超時

若是一個獲得鎖的線程在執行任務的過程當中掛掉,來不及顯式地釋放鎖,這塊資源將永遠被鎖住(死鎖),別的線程再也別想進來。因此,setnxkey 必須設置一個超時時間,以保證即便沒有被顯式釋放,這把鎖也要在必定時間後自動釋放。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)
    }
}

存在什麼問題

setnxexpire 的非原子性

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 是一種分佈式協調服務,用於管理大型主機。在分佈式環境中協調和管理服務是一個複雜的過程。Zookeeper 經過極其簡單的架構和 API 解決了這個問題。

Zookeeper 的數據模型

Zookeeper 的數據模型很像數據結構中的樹,也很像文件系統的目錄。

樹是由節點組成的,Zookeeper 的數據存儲也是一樣基於節點,這種節點佳做Znode。對於不一樣樹的節點,Znode 的引用方式是路徑引用,相似於文件路徑:/動物/貓 /汽車/寶馬

ZooKeeper數據模型

Znode包含哪些元素

  • data:Znode 存儲的數據信息。
  • ACL:記錄 Znode 的訪問權限,即那些人或哪些IP能夠訪問本節點。
  • stat:包含 Znode 的各類元數據,好比事務ID、版本號、時間戳、大小等。
  • child:當前節點的子節點引用。

注意:Zookeeper 是爲讀多寫少的場景所設計。Znode並非用來存儲大規模業務數據,而是用於存儲少許的狀態和配置信息,每一個節點的數據最大不能超過 1MB

ZooKeeper的Znode

Zookeeper 的基本操做

  • 建立節點 create
  • 刪除節點 delete
  • 判斷節點是否存在 exists
  • 得到一個節點的數據 getData
  • 設置一個節點的數據 setData
  • 獲取節點下的全部節點 getChildren

其中,exests , getData , getChildren 屬於讀操做。Zookeeper 客戶端在請求讀操做的時候,能夠選擇是否設置 Watch

Zookeeper 的事件通知

咱們能夠把 Watch 理解成是註冊在特定 Znode 上的觸發器。當這個 Znode 發生改變,也就是調用了 create , delete ,setData 等方法的時候,將會出發 Znode 上註冊的對應事件,請求 Watch 的客戶端會接受到異步通知。

具體交互過程:

  • 客戶端調用 getData 方法,watch 參數是 true。服務端接收到請求,返回節點數據,而且在對應的哈希表裏插入被 Watch 的 Znode 路徑,以及 Watcher 列表。
  • 當 Watch 的 Znode 已刪除,服務端會查找哈希表,找到該 Znode 對應的全部 Watcher,異步通知客戶端,而且刪除哈希表中對應的 Key-Value。

Zookeeper 的一致性

Zookeeper 身爲分佈式系統協調服務,爲了防止單機掛掉的狀況,Zookeeper 維護了一個集羣。

ZooKeeper的一致性

Zookeeper Service 集羣是一主多從結構。

在更新數據時,首先更新到主節點(這裏的節點指的是服務器,不是 Znode),在同步到從節點。

在讀取數據時,直接讀取任意從節點。

爲了保證主從節點的數據一致性,Zookeeper 採用 ZAB協議,這種協議很是相似於一致性算法那 Paxos 和 Raft。

什麼是ZAB協議

Zookeeper Atomic Broadcast,有效解決了 Zookeeper 集羣崩潰恢復,以及主從同步數據的問題。

ZAB 協議定義的三種節點狀態

  • Looking:選舉狀態。
  • Following:Follower節點(從節點)所處的狀態。
  • Leading:Leader節點(主節點)所處狀態。

最大 ZXID

最大 ZXID 也就是節點本地的最新事務編號,包含 epoch 和計數兩部分。epoch是紀元的意思,至關於Raft算法選主時候的 term。

ZAB 崩潰恢復

加入 Zookeeper 當前主節點掛掉了,集羣會進行奔潰恢復。ZAB 的奔潰恢復分紅三個階段:

  • Leader Election

    1. 選舉階段,此時集羣中的節點處於 Looking 狀態。它們會向其餘節點發起投票,投票當中包含本身的服務器ID 和最新事務ID(ZXID)。
    2. 接下來,節點會用自身的 ZXID 和從其餘節點接收到的 ZXID 做比較,若是發現別人家的 ZXID 比本身大,也就是數據比本身新,那麼就從新發起投票,投票給目前已知最大的ZXID所屬節點。
    3. 每次投票後,服務器都會統計投票數量,判斷是否有某個節點獲得半數以上的投票。若是存在這樣的節點,該節點就會成爲準 Leader,狀態變成 Leading。其餘節點的狀態變爲 Following。
  • Discovery

    發現階段,用於在從節點中發現最新的 ZXID和事務日誌(爲了防止某些意外的狀況,好比因網絡緣由在上一階段產生了多個Leader的狀況)。

    1. 在這一階段,Leader 集思廣益,接受全部Follower發來各自的最新 epoch 值。Leader從中選出最大的epoch,基於此值加 1,生成新的 epoch 分發給各個 Follower。
    2. 各個 Follower 收到全新的 epoch 後,返回 ACK 給Leader,帶上各自最大的 ZXID 和歷史事務日誌。Leader 選出最大的ZXID,並更新自身歷史日誌。
  • Synchronization

    同步階段,把 Leader 剛纔收集獲得的最新歷史事務日誌,同步給集羣中全部的 Follower。只有當半數 Follower 同步成功,這個準 Leader 才能成爲正式的 Leader。

    自此,故障恢復正式完成。

ZAB 的數據寫入

ZAB 的數據寫入涉及到 Broadcast 階段,簡單來講,就是 Zookeeper 常規狀況下更新數據的時候,由 Leader 廣播到全部的 Follower。其過程以下:

  • 客戶端發出寫入數據請求給任意 Follower。
  • Follower 把寫入數據請求轉發給 Leader。
  • Leader 採用二階段提交方式,先發送 Propose 廣播給 Follower。
  • Follower 接收到 Propose 消息,寫入日誌成功後,返回 ACK 消息給 Leader。
  • Leader 接收到半數以上 ACK 消息,返回成功給客戶端,而且廣播 Commit 請求給 Follower。

ZooKeeper的ZAB

總結

ZAB 協議既不是強一致性,也不是若一致性,而是處於二者之間的單調一致性(順序一致性)。它依靠事務 ID 和版本號,保證了數據的更新和讀取是有序的。

Zookeeper 的應用場景

分佈式鎖

利用 Zookeeper 的臨時順序節點,能夠輕鬆實現分佈式鎖。

服務註冊和發現

利用 Znode 和 Watcher,能夠實現分佈式服務的註冊和發現。

共享配置和狀態信息

Redis 的分佈式解決方案 Codis,就利用了 Zookeeper 來存放數據路由表和 codis-proxy 節點的元信息。同時 codis-config 發起的命令都會經過 Zookeeper 同步到各個存活的 codis-proxy。

此外, Kafka、HBase、Hadoop,也都依靠 Zookeeper 同步節點信息,實現高可用。

Zookeeper 分佈式鎖

什麼是臨時順序節點

Zookeeper 的數據結構就像一棵樹,這棵樹由節點組成,這種節點叫作 Znode。

Znode 分爲四種類型:

  • 持久節點 Persistent

    默認的節點類型。建立節點的客戶端與 Zookeeper 斷開鏈接後,該節點依舊存在。

  • 持久節點順序節點 Persistent_Sequential

    所謂順序節點,就是在建立節點時,Zookeeper 根據建立的時間順序給該節點名稱進行編號。

  • 臨時節點 Ephemeral

    與持久節點相反,當建立節點的客戶端與 Zookeeper 斷開鏈接後,臨時節點會被刪除。

  • 臨時順序節點 Ephemeral_Sequential

    臨時順序節點結合 臨時節點和順序節點的特色:在建立節點時,Zookeeper 根據建立的時間順序給該節點名稱進行編號;當建立節點的客戶端與 Zookeeper 斷開鏈接後,臨時節點會被刪除。

Zookeeper 分佈式鎖的原理

獲取鎖

  • 首先,在 Zookeeper 當中建立一個持久節點 ParentLock。當第一個客戶端想要得到鎖時,須要在 ParentLock 這個節點下面建立一個臨時順序節點 Lock1
  • 以後,Client1 查找 ParentLock 下面全部的臨時順序節點並排序,判斷本身所建立的節點 Lock1 是否是順序最靠前的一個。若是是第一個節點,則成功得到鎖。
  • 這時候,若是再有一個客戶端 Client2 前來獲取鎖,則在 ParentLock 下再建立一個臨時順序節點 Lock2。
  • Client2 查找 ParentLock 下面全部的臨時順序節點並排序,判斷本身所建立的節點 Lock2 是否是順序最靠前的一個,結果發現 Lock2 並非最小的。
  • 因而,Client2 向排序僅比它靠前的節點 Lock1 註冊 Watcher,用於監聽 Lock1 節點是否存在。這意味着 Client2 搶鎖失敗,進入了等待狀態。
  • 這時候,若是又有一個客戶端 Client3 前來獲取鎖,則在 ParentLock 下載再建立一個臨時順序節點 Lock3。
  • Client3 查找 ParentLock 下面全部的臨時順序節點並排序,判斷本身所建立的節點 Lock3 是否是順序最靠前的一個,結果一樣發現節點 Lock3 並非最小的。
  • 因而,Client3 向排序僅比它靠前的節點 Lock2 註冊 Watcher,用於監聽 Lock2 節點是否存在。這意味着 Client3 一樣搶鎖失敗,進入了等待狀態。
  • 這樣一來,Client1 獲得了鎖,Client2 監聽了 Lock1,Client3 監聽了 Lock2。這偏偏造成了一個等待隊列。

釋放鎖

釋放鎖的分爲兩種狀況:

任務完成,客戶端顯示釋放

當任務完成時,Client1 會顯示調用刪除節點 Lock1 的指令。

任務執行過程當中,客戶端崩潰

  • 得到鎖的 Client1 在執行任務過程當中,若是崩潰,則會斷開與 Zookeeper 服務端的連接。根據臨時節點的特性,相關聯的節點 Lock1 會隨之自動刪除。

  • 因爲 Client2 一致監聽着 Lock1 的存在狀態,當 Lock1 節點被刪除, Client2 會當即收到通知。
  • 這時候Client2 會再次查詢 ParentLock 下面的全部節點,確認本身建立的 Lock2 是否是目前最小的節點。若是是最小,則 Client2 瓜熟蒂落地得到了鎖。

Zookeeper 和 Redis 分佈式鎖的比較

分佈式鎖 優勢 缺點
Zookeeper 1. 有封裝好的框架,容易實現。
2. 有等待鎖的隊列,大大提高搶鎖效率。
添加和刪除節點性能低。
Redis Set 和 Del 指令的性能較高。 1. 實現複雜,須要考慮超時、原子性、誤刪等情形。
2. 沒有等待鎖的隊列,只能在客戶端自旋來等鎖,效率低下。

本文整理自 (https://www.funtl.com/zh/apache-dubbo-zookeeper/)

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息