一文帶你認識zookeeper並探究分散式鎖實現原理(附docker構建zookeeper教程)

大能貓貓發表於2020-12-22

你瞭解zookeeper嗎?
你真的瞭解zookeeper嗎?
如果你非常理解,那麼請划走,我怕你嘲笑我~
如果你也一知半解,那就往下進行了。

先來看下概念

ZooKeeper是一個分散式的,開放原始碼的分散式應用程式協調服務,是Google的Chubby一個開源的實現,是Hadoop和Hbase的重要元件。它是一個為分散式應用提供一致性服務的軟體,提供的功能包括:配置維護、域名服務、分散式同步、組服務等。
ZooKeeper的目標就是封裝好複雜易出錯的關鍵服務,將簡單易用的介面和效能高效、功能穩定的系統提供給使用者。
ZooKeeper包含一個簡單的原語集,提供Java和C的介面。
ZooKeeper程式碼版本中,提供了分散式獨享鎖、選舉、佇列的介面,程式碼在$zookeeper_home\src\recipes。其中分佈鎖和佇列有Java和C兩個版本,選舉只有Java版本。

上邊概念來自百度百科
不要噓!
看看我的理解

假設我們是一個團隊(team),這個團隊需要一個(領導者,也可以叫領袖)leader,leader是幹嘛用的?管理所有團隊人員什麼的我們先不說,假如,董事會要了解你們這個團隊的情況,研究研究1024節日給安排幾個鼓勵師,那麼行政肯定會先去找leader,誰讓leader知道的最多呢,並且,leader的回答可信度更高。
leader負責人員 資源排程,比如,客戶要加需求,leader就先派一個技術過去討論技術可行性,發現小胡工作正在空閒期,那好,就安排小胡過去,
過了一會
產品經理giao哥也過來說需要一個java來改需求,leader發現,已經沒有人員可以給giao哥了,就跟giao哥說,沒人了,都被要走了,你自己去協商吧。
giao哥:一giu窩裡giao giao!!!!!!!!(很生氣)
如果leader仍然把小胡調配給giao哥,那麼客戶和giao哥可能就會一起同臺rap,場面有點殘忍。。。。
所以,leader的作用就是協調 協調 協調

在分散式系統中,同樣需要一個leader來協調

同樣的,在分散式系統中,也需要這樣的協調者,來回答系統下各個節點的提問。

比如我們搭建了一個資料庫叢集,裡面有一個Master,多個Slave,Master負責寫,Slave只讀,我們需要一個系統,來告訴客戶端,哪個是Master。

有人說,很簡單,我們把這個資訊寫到一個Java伺服器的記憶體就好了,用一個map,key:master,value:master機器對應的ip

但是別忘了,這是個單機,一旦這個機器掛了,就完蛋了,客戶端將無法知道到底哪個是Master。

於是開始進行擴充,擴充成三臺伺服器的叢集。

這下問題來了,如果我在其中一臺機器修改了Master的ip,資料還沒同步到其他兩臺,這時候客戶端過來查詢,如果查詢走的是另外兩臺還沒有同步到的機器,就會拿到舊的資料,往已經不是master的機器寫資料。

所以我們需要這個儲存master資訊的伺服器叢集,做到當資訊還沒同步完成時,不對外提供服務,阻塞住查詢請求,等待資訊同步完成,再給查詢請求返回資訊。

這樣一來,請求就會變慢,變慢的時間取決於什麼時候這個叢集認為資料同步完成了。

假設這個資料同步時間無限短,比如是1微妙,可以忽略不計,那麼其實這個分散式系統,就和我們之前單機的系統一樣,既可以保證資料的一致,又讓外界感知不到請求阻塞,同時,又不會有SPOF(Single Point of Failure)的風險,即不會因為一臺機器的當機,導致整個系統不可用。

這樣的系統,就叫分散式協調系統。誰能把這個資料同步的時間壓縮的更短,誰的請求響應就更快,誰就更出色,Zookeeper就是其中的佼佼者。

它用起來像單機一樣,能夠提供資料強一致性,但是其實背後是多臺機器構成的叢集,不會有SPOF。

其實就是CAP理論中,滿足CP,不滿足A的那類分散式系統。

如果把各個節點比作各種小動物,那協調者,就是動物園管理員,這也就是Zookeeper名稱的由來了,從名字就可以看出來它的雄心勃勃。

看看zookeeper架構圖

在這裡插入圖片描述

  • Leader: ZooKeeper 叢集工作的核心
    事務請求(寫操作)的唯一排程和處理者,保證叢集事務處理的順序性;叢集內部各個服務的排程者。 對於
    create,setData,delete 等有寫操作的請求,則需要統一轉發給 leader 處理,leader
    需要決定編號、執行操作,這個過程稱為一個事務。
  • Follower: 處理客戶端非事務(讀操作)請求 轉發事務請求給 Leader 參與叢集 leader 選舉投票2n-1臺可以做叢集投票
    此外,針對訪問量比較大的 zookeeper 叢集,還可以新增觀察者角色
  • Observer:
    觀察者角色,觀察ZooKeeper叢集的最新狀態變化並將這些狀態同步過來,其對於非事務請求可以進行獨立處理,對於事務請求,則會轉發給Leader伺服器處理
    不會參與任何形式的投票只提供服務,通常用於在不影響叢集事務處理能力的前提下提升叢集的非事務處理能力 扯淡:說白了就是增加併發的請求

主從與主備

  • 主從:主節點少,從節點多,主節點分配任務,從節點具體執行任務
  • 主備:主節點與備份節點,主要用於解決我們主機節點掛掉以後,如何選出來一個新的主節點的問題,保證我們的主節點不會當機

很多時候,主從與主備沒有太明顯的分界線,很多時候都是一起出現

zookeeper特性

  • 全域性資料的一致:每個 server 儲存一份相同的資料副本,client 無論連結到哪個 server,展示的資料都是一致的
  • 可靠性:如果訊息被其中一臺伺服器接受,那麼將被所有的伺服器接受
  • 順序性:包括全域性有序和偏序兩種:全域性有序是指如果在一臺伺服器上訊息 a 在訊息 b 前釋出,則在所有 server 上訊息 a 在訊息 b
    前被髮布,偏序是指如果以個訊息 b 在訊息 a 後被同一個傳送者釋出,a 必須將排在 b 前面
  • 資料更新原子性:一次資料更新要麼成功,要麼失敗,不存在中間狀態
  • 實時性:ZooKeeper 保證客戶端將在一個時間間隔範圍內獲得伺服器的更新資訊,或者伺服器失效的資訊

分散式應用的優點

  • 可靠性 - 單個或幾個系統的故障不會使整個系統出現故障。
  • 可擴充套件性 - 可以在需要時增加效能,通過新增更多機器,在應用程式配置中進行微小的更改,而不會有停機時間。
  • 透明性 - 隱藏系統的複雜性,並將其顯示為單個實體/應用程式。

分散式應用的挑戰

  • 競爭條件 - 兩個或多個機器嘗試執行特定任務,實際上只需在任意給定時間由單個機器完成。例如,共享資源只能在任意給定時間由單個機器修改。
  • 死鎖 - 兩個或多個操作等待彼此無限期完成。
  • 不一致 - 資料的部分失敗。
    什麼是Apache ZooKeeper?
    Apache ZooKeeper是由叢集(節點組)使用的一種服務,用於在自身之間協調,並通過穩健的同步技術維護共享資料。ZooKeeper本身是一個分散式應用程式,為寫入分散式應用程式提供服務。

ZooKeeper提供的常見服務如下 :

  • 命名服務 - 按名稱標識叢集中的節點。它類似於DNS,但僅對於節點。
  • 配置管理 - 加入節點的最近的和最新的系統配置資訊。
  • 叢集管理 - 實時地在叢集和節點狀態中加入/離開節點。
  • 選舉演算法 - 選舉一個節點作為協調目的的leader。
  • 鎖定和同步服務 - 在修改資料的同時鎖定資料。此機制可幫助你在連線其他分散式應用程式(如Apache HBase)時進行自動故障恢復。
  • 高度可靠的資料登錄檔 - 即使在一個或幾個節點關閉時也可以獲得資料。
  • 分散式應用程式提供了很多好處,但它們也丟擲了一些複雜和難以解決的挑戰。ZooKeeper框架提供了一個完整的機制來克服所有的挑戰。競爭條件和死鎖使用故障安全同步方法進行處理。另一個主要缺點是資料的不一致性,ZooKeeper使用原子性解析。

ZooKeeper的好處

  • 簡單的分散式協調過程
  • 同步 - 伺服器程式之間的相互排斥和協作。此過程有助於Apache HBase進行配置管理。
  • 有序的訊息
  • 序列化 - 根據特定規則對資料進行編碼。確保應用程式執行一致。這種方法可以在MapReduce中用來協調佇列以執行執行的執行緒。
  • 可靠性
  • 原子性 - 資料轉移完全成功或完全失敗,但沒有事務是部分的。

使用zookeeper製作分散式鎖

我們使用docker構建,如果沒有docker環境,建議先搭建一套,可以參考我的這篇部落格:
XXX(這個還沒寫,先準備上)
執行

docker pull zookeeper

輸入 docker ps -a檢視是否ok

docker ps -a

啟動容器(Server端),預設埠2181

docker run --name my_zookeeper -d zookeeper:latest

建立Client端並連線server端

docker run -it --rm --link my_zookeeper:zookeeper zookeeper zkCli.sh -server zookeeper

zookeeper叢集配置

建立docker-compose.yml檔案

version: '2'
services:
    zoo1:
        image: zookeeper
        restart: always
        container_name: zoo1
        ports:
            - "2181:2181"
        environment:
            ZOO_MY_ID: 1
            ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
 
    zoo2:
        image: zookeeper
        restart: always
        container_name: zoo2
        ports:
            - "2182:2181"
        environment:
            ZOO_MY_ID: 2
            ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
 
    zoo3:
        image: zookeeper
        restart: always
        container_name: zoo3
        ports:
            - "2183:2181"
        environment:
            ZOO_MY_ID: 3
            ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888

配置檔案解讀:

  • 執行三個 zookeeper 映象,
  • 將本地的 2181, 2182, 2183 埠繫結到對應的容器的2181埠上.
  • ZOO_MY_ID 和 ZOO_SERVERS 是搭建 ZK 叢集需要設定的兩個環境變數, 其中 ZOO_MY_ID 表示 ZK 服務的id, 它是1-255 之間的整數, 必須在叢集中唯一. ZOO_SERVERS 是ZK 叢集的主機列表.

繼續
我們在docker-compose.yml檔案同級目錄下執行

COMPOSE_PROJECT_NAME=zk_test docker-compose up

nice~~~
已經啟動三個zookeeper了
報告報告:我們已經乘坐上火箭上天了~

如果你想檢視這幾個容器,執行下邊這條命令

docker-compose ps

使用zookeeper作為分散式鎖

我們先了解一下什麼是分散式鎖

概念

分散式鎖是控制分散式系統之間同步訪問共享資源的一種方式。

條件

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

注意點

在實現分散式鎖的過程中需要注意的:

鎖的可重入性(遞迴呼叫不應該被阻塞、避免死鎖)
鎖的超時(避免死鎖、死迴圈等意外情況)
鎖的阻塞(保證原子性等)
鎖的特性支援(阻塞鎖、可重入鎖、公平鎖、聯鎖、訊號量、讀寫鎖)

在使用分散式鎖時需要注意:

分散式鎖的開銷(分散式鎖一般能不用就不用,有些場景可以用樂觀鎖代替)
加鎖的粒度(控制加鎖的粒度,可以優化系統的效能)
加鎖的方式

實現分散式鎖的方式

  • 快取(Redis等)實現分散式鎖;
  • 資料庫實現分散式鎖;—不推薦使用
  • Zookeeper實現分散式鎖;

zookeeper的儲存結構

zookeeper的儲存結構像一棵樹Tree,由節點組成,節點我們稱之為Znode

節點分類

  • 持久節點
  • 持久順序節點 建立節點時,Zookeeper根據建立的時間順序給該節點名稱進行編號:
  • 臨時節點 斷開連線,節點刪除
  • 臨時順序節點

zookeeper實現分散式鎖思路

每個客戶端對某個方法加鎖時

在 Zookeeper 上與該方法對應的指定節點的目錄下

生成一個唯一的臨時有序節點

判斷是否獲取鎖的方式很簡單
只需要判斷有序節點中序號最小的一個。
當釋放鎖的時候,只需將這個臨時節點刪除即可。
同時,其可以避免服務當機導致的鎖無法釋放,而產生的死鎖問題

zookeeper實現分散式鎖優缺點

優點:

  • 有效的解決單點問題,不可重入問題,非阻塞問題以及鎖無法釋放的問題
  • 實現較為簡單

缺點:

  • 效能上不如使用快取實現的分散式鎖,因為每次在建立鎖和釋放鎖的過程中,都要動態建立、銷燬臨時節點來實現鎖功能
  • 需要對Zookeeper的原理有所瞭解

實現原理

在這裡插入圖片描述

第一步:

我們先建立一個持久節點FatherLock,當客戶端A想要獲取鎖時,需要在FatherLock下建立一個臨時順序節點LockA,客戶端A會查詢FatherLock下面所有順序節點並排序,判斷自己所建立的是不是第一個,如果是,則成功獲取鎖。

第二步:

在這裡插入圖片描述
客戶端B想獲取鎖時會在FatherLock下建立LockB鎖
客戶端B會查詢FatherLock下面所有順序節點並排序,判斷自己所建立的是不是第一個。
如果是,則成功獲取鎖。
如果不是,則在它的前一位節點註冊watcher 用於監聽前一位節點是否存在,相當於進入等待狀態。

第三步:

在這裡插入圖片描述
客戶端C來了,~~~建立Lock3 在LockB下注冊watcher 監聽

第四步:

是不是發現實現了一個佇列
A得到了鎖
B監聽A
C監聽B

第五步 釋放鎖:

A拿到鎖->程式碼執行完畢->呼叫刪除LockA
A拿到鎖->異常 斷聯->自動刪除LockA
LockA刪除後
LockB會立刻收到通知,客戶端B會立刻拿到鎖
同理—

我叫你一聲靚仔,幫我點個贊可好?

相關文章