ZooKeeper分散式專題(二) -- zookeeper應用場景及資料模型

H小永發表於2019-07-16

ZooKeeper分散式專題與Dubbo微服務入門

zookeeper基本資料型別

  • zookeeper是一個樹形結構,類似於前端開發中的 tree.js 元件;

    image.png

  • zk的資料模型也可以理解為 linux/unix 的檔案目錄:/usr/local

  • 每一個節點都稱之為 znode,它可以有子節點,也可以有資料

    子節點: 就是父目錄下的一個子目錄,在 zk中稱之為節點,每一個節點中都有一些相應的資料,就像目錄下有一些檔案資料一樣

  • 每個節點分為臨時節點和永久節點,臨時節點在客戶端斷開後消失

    永久節點:其實就是一個持久化的過程,我們存了一些資料,只有人為的情況在才會刪除 如是session超時或者是session丟失,資料還是會一直存在的。臨時節點生命週期依賴建立它的會話,一旦會話結束,臨時節點將會被刪除。臨時節點不允許有子節點。

  • 每個zk節點都是有各自的版本號,可以通過命令列來顯示節點資訊;

    節點的資訊其實就是節點的詳情,詳情中包含了一些版本號,版本號是累加的,每當節點中的資料發生變化,版本號就會累加(樂觀鎖)

  • 刪除/修改過時節點,版本號不匹配則會報錯

    例如我們在查詢某一個節點的時候,比如說它的節點是1,經過兩個人進行刪除或者修改之後,那麼它的節點會由1變為2,在變為3,此時我們需要去修改或者刪除這個節點,那麼刪除這個節點的時候,我們傳入的版本號是一個老的版本號,那麼這個時候就會報一個版本號不匹配的異常,這也是在資料庫中是使用樂觀鎖的一種表現

  • 每一個zk節點儲存的資料不宜過大,幾k即可(官方推薦);

  • 節點是可以設定許可權acl ,可以通過許可權來限定使用者的訪問

    acl:許可權控制列表,後續會講到;

zookeeper應用場景

Master選舉

在分散式系統中,Master往往用來協調叢集中其他系統單元,具有對分散式系統狀態變更的決定權,如在讀寫分離的應用場景中,客戶端的寫請求往往是由Master來處理,或者其常常處理一些複雜的邏輯並將處理結果同步給其他系統單元。利用Zookeeper的強一致性,能夠很好地保證在分散式高併發情況下節點的建立一定能夠保證全域性唯一性,即Zookeeper將會保證客戶端無法重複建立一個已經存在的資料節點。

  首先建立/master_election/2019-07-05節點,客戶端叢集每天會定時往該節點下建立臨時節點,如/master_election/2019-07-05/binding,這個過程中,只有一個客戶端能夠成功建立,此時其變成master,其他節點都會在節點/master_election/2019-07-05上註冊一個子節點變更的Watcher,用於監控當前的Master機器是否存活,一旦發現當前Master掛了,其餘客戶端將會重新進行Master選舉。

這也就是所謂的首腦模式,從而保證我們的叢集是高可用的;

image.png

資料釋出/訂閱(以Dubbo註冊中心為例)

資料釋出/訂閱系統,即配置中心。需要釋出者將資料釋出到Zookeeper的節點上,供訂閱者進行資料訂閱,進而達到動態獲取資料的目的,實現配置資訊的集中式管理和資料的動態更新。釋出/訂閱一般有兩種設計模式:推模式和拉模式,服務端主動將資料更新傳送給所有訂閱的客戶端稱為推模式;客戶端主動請求獲取最新資料稱為拉模式,Zookeeper採用了推拉相結合的模式,客戶端向服務端註冊自己需要關注的節點,一旦該節點資料發生變更,那麼服務端就會向相應的客戶端推送Watcher事件通知,客戶端接收到此通知後,主動到服務端獲取最新的資料。

  若將配置資訊存放到Zookeeper上進行集中管理,在通常情況下,應用在啟動時會主動到Zookeeper服務端上進行一次配置資訊的獲取,同時,在指定節點上註冊一個Watcher監聽,這樣在配置資訊發生變更,服務端都會實時通知所有訂閱的客戶端,從而達到實時獲取最新配置的目的。

Dubbo是集團開源的分散式服務框架,致力於提供高效能和透明化的遠端服務呼叫解決方案和基於服務框架展開的完整SOA服務治理方案。

其中服務自動發現是最核心的模組之一,該模組提供基於註冊中心的目錄服務,使服務消費方能夠動態的查詢服務提供方,讓服務地址透明化,同時服務提供方可以平滑的對機器進行擴容和縮容,其註冊中心可以基於提供的外部介面來實現各種不同型別的註冊中心,例如資料庫、ZK和Redis等。接下來看一下基於ZK實現的Dubbo註冊中心。

image.png

/dubbo: 這是Dubbo在ZK上建立的根節點。

/dubbo/com.foo.BarService:這是服務節點,代表了Dubbo的一個服務。

/dubbo/com.foo.BarService/Providers:這是服務提供者的根節點,其子節點代表了每個服務的真正的提供者。

/dubbo/com.foo.BarService/Comsumers:這是服務消費者的根節點,其子節點代表了每一個服務的真正的消費者。

Dubbo基於ZK實現註冊中心的工作流程:

服務提供者:在初始化啟動的時候首先在/dubbo/com.foo.BarService/Providers節點下建立一個子節點,同時寫入自己的URL地址,代表這個服務的一個提供者。

服務消費者 : 在啟動的時候讀圖並訂閱zookeeper上/dubbo/com.foo.BarService/Providers節點下的所有節點,並解析所有提供者的URL地址作為該服務類的地址列表,開始發起正常的呼叫。同時在Consumers節點下建立一個臨時節點,寫入自己的URL地址,代表自己是BarService的一個消費者

監控中心 : 監控中心是Dubbo服務治理體系的重要一部分,它需要知道一個服務的所有提供者和訂閱者及變化情況。監控中心在啟動的時候會通過ZK的/dubbo/com.foo.BarService節點來獲取所有提供者和消費者的url地址,並註冊Watcher來監聽其子節點變化情況。

所有服務提供者在ZK上建立的節點都是臨時節點,利用的是臨時節點的生命週期和客戶端會話繫結的特性,一旦提供者機器掛掉無法對外提供服務時該臨時節點就會從ZK上摘除,這樣服務消費者和監控中心都能感知到服務提供者的變化。

命名服務

命名服務也是分散式系統中比較常見的一類場景,被命名的實體通常可以是叢集中的機器、提供的服務地址或遠端物件,其中較為常見的是一些分散式服務框架中的服務地址列表,通過使用命名服務客戶端應用能夠制定名字來獲取資源的實體、服務地址和提供者的資訊等。

上層應用使用命名服務時可能僅需要一個全域性唯一的名字,類似於資料庫中的唯一主鍵,用資料庫自增id是可以的,但分庫分表的情況下就無法依靠資料庫的自增屬性來唯一標識一條記錄了。另外UUID也是一種廣泛應用的ID實現方式,但如果是用UUID對服務進行命名的話就太不直觀了,從字面意思根本看不出其表達的含義。下面看下用ZK如何實現全域性唯一ID的生成。

之前在ZNode介紹時提過,建立節點時可以設定為SEQUENTIAL順序節點,建立後API會返回這個節點的完整名字,利用這個特性我們就可以來生成全域性唯一ID了。

image.png

所有客戶端根據自己的任務型別,在指定型別的任務下建立一個順序節點,例如“Job-”節點

節點建立完畢後會返回一個完整的節點名稱,如Job-0000000001

客戶端拿到這個返回值後拼接上type型別,例如type1-Job-000000001,這樣就可以作為一個全域性唯一的ID了

在ZK中每個資料節點都能維護一份子節點的順序序列,當客戶端對其建立一個順序子節點時ZK會自動以字尾的形式在其子節點上新增一個序號,該場景就利用了ZK的這個特性。

分散式鎖

分散式鎖用於控制分散式系統之間同步訪問共享資源的一種方式,可以保證不同系統訪問一個或一組資源時的一致性,主要分為排它鎖和共享鎖。

排它鎖又稱為寫鎖或獨佔鎖,若事務T1對資料物件O1加上了排它鎖,那麼在整個加鎖期間,只允許事務T1對O1進行讀取和更新操作,其他任何事務都不能再對這個資料物件進行任何型別的操作,直到T1釋放了排它鎖。

如果不同系統或同一系統不同機器之間共享了同一資源,那訪問這些資源時通常需要一些互斥手段來保證一致性,這種情況下就需要用到分散式鎖了。

使用關係型資料庫是一種簡單、廣泛的實現方案,但大多數大型分散式系統中資料庫已經是效能瓶頸了,如果再給資料庫新增額外的鎖會更加不堪重負;另外,使用資料庫做分散式鎖,當搶到鎖的機器掛掉的話如何釋放鎖也是個頭疼的問題。

接下來看下使用ZK如何實現排他鎖。排他鎖的核心是如何保證當前有且只有一個事務獲得鎖,並且鎖被釋放後所有等待獲取鎖的事務能夠被通知到。

image.png

  • 獲取鎖,在需要獲取排它鎖時,所有客戶端通過呼叫介面,在/exclusive_lock節點下建立臨時子節點/exclusive_lock/lock。Zookeeper可以保證只有一個客戶端能夠建立成功,沒有成功的客戶端需要註冊/exclusive_lock節點監聽。

  • 釋放鎖,當獲取鎖的客戶端當機或者正常完成業務邏輯都會導致臨時節點的刪除,此時,所有在/exclusive_lock節點上註冊監聽的客戶端都會收到通知,可以重新發起分散式鎖獲取。

共享鎖又稱為讀鎖,若事務T1對資料物件O1加上共享鎖,那麼當前事務只能對O1進行讀取操作,其他事務也只能對這個資料物件加共享鎖,直到該資料物件上的所有共享鎖都被釋放。

image.png

  • 獲取鎖: 在需要獲取共享鎖時,所有客戶端都會到/shared_lock下面建立一個臨時順序節點,如果是讀請求,那麼就建立例如/shared_lock/host1-R-00000001的節點,如果是寫請求,那麼就建立例如/shared_lock/host2-W-00000002的節點。

  • 判斷讀寫順序:不同事務可以同時對一個資料物件進行讀寫操作,而更新操作必須在當前沒有任何事務進行讀寫情況下進行,通過zookeeper來確定分散式讀寫順序,大致分為四步。

    • 建立完節點後,獲取/shared_lock節點下所有位元組點,並對該節點變更註冊監聽。

    • 確定自己的節點序號在所有子節點中的順序

    • 對於讀請求:若沒有比自己序號小的子節點或者所有比自己序號小的子節點都是讀請求,那麼表明自己已經成功獲取共享鎖,同時開始執行讀取邏輯,若有寫請求,則需要等待。對於寫請求:若自己不是序號最小的子節點,那麼需要等待。

    • 接收到Watcher通知後,重複步驟1.

  • 釋放鎖:其釋放鎖的流程與獨佔鎖的流程一致。

ZooKeeper分散式專題與Dubbo微服務入門

相關文章