大資料基礎學習-4.Zookeeper-3.4.5

閒人勿-發表於2018-04-25

一、Zookeeper-3.4.5

1.分散式協調技術

在開始zookeeper前,首先了解分散式協調技術。分散式協調技術主要用來解決分散式環境中多個程式之間的同步控制,讓他們有序的去訪問某個資源,防止造成"髒資料"。


在圖中有三臺機器,每臺機器各跑一個應用程式。這三臺機器通過網路連線起來,構成一個系統來為使用者提供服務,這種系統稱作分散式系統。

假設在第一臺機器上掛載了一個資源,三個物理分佈的程式都要獲得這個資源,但同時進行訪問就會造成“髒資料”結果。這時候就需要一個協調器,來讓他們有序的來訪問這個資源。這個協調器就是,比如說"程式-1"在使用該資源的時候,會先去獲得鎖,"程式1"獲得鎖以後會對該資源保持獨佔,這樣其他程式就無法訪問該資源,"程式1"用完該資源以後就將鎖釋放掉,讓其他程式來獲得鎖,那麼通過這個鎖機制,就能保證了分散式系統中多個程式能夠有序的訪問該資源。分散式環境下的這個鎖叫作分散式鎖,也就是分散式協調技術實現的核心內容。

分散式協調遠比在同一臺機器上對多個程式的排程要難得多,而且如果為每一個分散式應用都開發一個獨立的協調程式。一方面,協調程式的反覆編寫浪費,且難以形成通用、伸縮性好的協調器。另一方面,協調程式開銷比較大,會影響系統原有的效能。所以,急需一種高可靠、高可用的通用協調機制來協調分散式應用。

我們所需要的分散式鎖服務概括起來就是:一個鬆散耦合的分散式系統中粗粒度鎖以及可靠性儲存(低容量)的系統 。鬆散耦合是指對硬體效能的依賴性不強;分散式系統即分散式鎖工作在分散式系統中;程式空間裡的鎖為細粒度鎖,屬於執行緒級的鎖,粗粒度鎖相對於細粒度鎖來說,只需保證機器之間能順利的協調工作就可以,而沒必要細緻到執行緒級別,因此說它是粗粒度的;可靠性儲存是指這個系統能夠存一些資料。

針對以上的需求,Google內部的分散式鎖服務實現叫Chubby,基於Paxos協議,Apache的分散式鎖服務叫ZooKeeper,基於Zab協議(ZooKeeper Atomic Broadcast 。Chubby是非開源的,Google自家用,雅虎模仿Chubby開發出了ZooKeeper,也實現了類似的分散式鎖的功能,並且將ZooKeeper作為開源程式捐獻給 Apache。

2.Zookeeper概述

ZooKeeper是一種為分散式應用所設計的高可用、高效能且一致的開源協調服務,它提供了一項基本服務:分散式鎖服務。由於ZooKeeper的開源特性,在分散式鎖的基礎上,摸索了出了其他的使用方法:配置維護、組服務、分散式訊息佇列、分散式通知/協調等

ZooKeeper效能上的特點決定了它能夠用在大型的、分散式的系統當中。從可靠性方面來說,它並不會因為一個節點的錯誤而崩潰。除此之外,它嚴格的序列訪問控制意味著複雜的控制原語可以應用在客戶端上。ZooKeeper在一致性、可用性、容錯性的保證,也是ZooKeeper的成功之處,它獲得的一切成功都與它採用的協議——Zab協議是密不可分的

ZooKeeper設計一種新的資料結構——Znode,然後在該資料結構的基礎上定義了一些原語,也就是一些關於該資料結構的一些操作。有了這些資料結構和原語還不夠,因為ZooKeeper是工作在一個分散式的環境下,服務是通過訊息以網路的形式傳送給分散式應用程式,所以還需要一個通知機制——Watcher機制。那麼總結一下,ZooKeeper所提供的服務主要是通過:資料結構+原語+watcher機制,三個部分來實現的。

3.Zookeeper核心概念

ZooKeeper擁有一個層次的名稱空間,這個和標準的檔案系統非常相似


從圖中可以看出ZooKeeper的資料模型,在結構上和標準檔案系統的非常相似,都是採用這種樹形層次結構,ZooKeeper樹中的每個節點被稱為—Znode。和檔案系統的目錄樹一樣,ZooKeeper樹中的每個節點可以擁有子節點。但也有不同之處:

1) 引用方式

Znode通過路徑引用,如同Unix中的檔案路徑。路徑必須是絕對的,因此他們必須由斜槓字元來開頭。除此以外,他們必須是唯一的,也就是說每一個路徑只有一個表示,因此這些路徑不能改變。在ZooKeeper中,路徑由Unicode字串組成,並且有一些限制。字串"/zookeeper"用以儲存管理資訊,比如關鍵配置資訊。

2) Znode結構

ZooKeeper名稱空間中的Znode,兼具檔案和目錄兩種特點。既像檔案一樣維護著資料、元資訊、ACL、時間戳等資料結構,又像目錄一樣可以作為路徑標識的一部分。圖中的每個節點稱為一個Znode。每個Znode由3部分組成:

① stat:此為狀態資訊, 描述該Znode的版本, 許可權等資訊

② data:與該Znode關聯的資料

③ children:該Znode下的子節點

ZooKeeper雖然可以關聯一些資料,但並沒有被設計為常規的資料庫或者大資料儲存,相反的是,它用來管理排程資料,比如分散式應用中的配置檔案資訊、狀態資訊、彙集位置等等。這些資料的共同特性就是它們都是很小的資料,通常以KB為大小單位。ZooKeeper的伺服器和客戶端都被設計為嚴格檢查並限制每個Znode的資料大小至多1M,但常規使用中應該遠小於此值。

3) 資料訪問

ZooKeeper中的每個節點儲存的資料要被原子性的操作。也就是說讀操作將獲取與節點相關的所有資料,寫操作也將替換掉節點的所有資料。另外,每一個節點都擁有自己的ACL(訪問控制列表),這個列表規定了使用者的許可權,即限定了特定使用者對目標節點可以執行的操作。


當客戶端請求讀取特定 znode 的內容時,讀取操作是在客戶端所連線的伺服器上進行的。因此,由於只涉及集合體中的一個伺服器,所以讀取是快速和可擴充套件的。然而,為了成功完成寫入操作,要求 ZooKeeper 集合體的嚴格意義上的多數節點都是可用的。在啟動 ZooKeeper 服務時,集合體中的某個節點被選舉為領導者。當客戶端發出一個寫入請求時,所連線的伺服器會將請求傳遞給領導者。此領導者對集合體的所有節點發出相同的寫入請求。如果嚴格意義上的多數節點(也被稱為法定數量(quorum))成功響應該寫入請求,那麼寫入請求被視為已成功完成。然後,一個成功的返回程式碼會返回給發起寫入請求的客戶端。如果集合體中的可用節點數量未達到法定數量,那麼 ZooKeeper 服務將不起作用。

ZooKeeper有這樣一個特性:叢集中只要有超過過半的機器是正常工作的,那麼整個叢集對外就是可用的, 也就是說如果有2個ZooKeeper,那麼只要有1個死了zookeeper就不能用了,因為1沒有過半,所以2個ZooKeeper的死亡容忍度為0;同理,要是有3個ZooKeeper,一個死了,還剩下2個正常的,過半了,所以3個ZooKeeper的容忍度為1;同理你多列舉幾個:2->0;3->1;4->1;5->2;6->2會發現一個規律,2n和2n-1的容忍度是一樣的,都是n-1,所以為了更加高效,通常都使用技術個ZooKeeper。  

【如果執行一臺伺服器,從ZooKeeper 的角度來看是沒問題的;只是系統不再是高可靠或高可用的。三個節點的 ZooKeeper 集合體支援在一個節點故障的情況下不丟失服務,這對於大多數使用者而言,這可能是沒問題的,也可以說是最常見的部署拓撲。不過,為了安全起見,可以在集合體中使用五個節點。五個節點的集合體可以拿出一臺伺服器進行維護或滾動升級,並能夠在不中斷服務的情況下承受第二臺伺服器的意外故障。因此,在 ZooKeeper 集合體中,三、五或七是最典型的節點數量。ZooKeeper 集合體的大小與分散式系統中的節點大小沒有什麼關係。分散式系統中的節點將是 ZooKeeper 集合體的客戶端,每個 ZooKeeper 伺服器都能夠以可擴充套件的方式處理大量客戶端。例如,HBase(Hadoop 上的分散式資料庫)依賴​​於ZooKeeper 實現區域伺服器的領導者選舉和租賃管理。可以利用一個相對較少(比如說,五個)節點的 ZooKeeper 集合體執行有 50 個節點的大型 HBase 叢集。】

4) 節點型別

ZooKeeper中的節點有兩種,分別為臨時節點和永久節點。節點的型別在建立時即被確定,並且不能改變。

① 臨時節點Ephemeral Nodes:該節點的生命週期依賴於建立它們的會話。一旦會話(Session)結束,臨時節點將被自動刪除,當然可以也可以手動刪除。雖然每個臨時的Znode都會繫結到一個客戶端會話,但他們對所有的客戶端還是可見的。如果會話又重新恢復,臨時節點不會恢復,需要再進行註冊。另外,ZooKeeper的臨時節點不允許擁有子節點

② 永久節點Persistent Nodes:該節點的生命週期不依賴於會話,並且只有在客戶端執行刪除操作的時候,他們才能被刪除。

③ 順序節點Sequence Nodes(不能單獨存在,需與上面兩種節點組合),client申請建立該節點時,zk會自動在節點路徑末尾新增遞增序號,這種型別是實現分散式鎖,分散式queue等特殊功能的關鍵。

5) watch

客戶端可以在節點上設定watch,稱之為監視器。當節點狀態發生改變時(Znode的增、刪、改)將會觸發watch所對應的操作。當watch被觸發時,ZooKeeper將會向客戶端傳送且僅傳送一條通知,客戶端被動收到通知,watch只能被觸發一次,這樣可以減少網路流量,觸發監控後需要重新設定。

4.Zookeeper的角色

5.節點重要屬性

屬性 描述
czxid 節點被建立的Zxid值
mzxid  節點被修改的Zxid值
pzxid子節點(或自身)被修改的時間
ctime節點被建立的時間
mtime 節點最後一次被修改的時間
version節點被修改的版本號
cversion節點的所擁有子節點被修改的版本號
aversion
節點的ACL被修改的版本號
emphemeralOwner 如果此節點為臨時節點,那麼它的值為這個節點擁有者的會話ID;否則,它的值為0
dataLength節點資料域的長度
numChildren節點擁有的子節點個數

1) Zxid

致使ZooKeeper節點狀態改變的每一個操作都將使節點接收到一個Zxid格式的時間戳,並且這個時間戳全域性有序。也就是說,每個對節點的改變都將產生一個唯一的Zxid。如果Zxid1的值小於Zxid2的值,那麼Zxid1所對應的事件發生在Zxid2所對應的事件之前。實際上,ZooKeeper的每個節點維護者三個Zxid值,為別為:cZxid、mZxid、pZxid。

① cZxid:是節點的建立時間所對應的Zxid格式時間戳。

② mZxid:是節點的修改時間所對應的Zxid格式時間戳。

③ pZxid:這個值只和該節點的子節點有關,是該節點的子節點(如果沒有子節點及時節點本身)最近一次 【建立/刪除 】的時間戳。【只與本節點 或者該節點的子節點有關,與孫子節點無關】

實現中Zxid是一個64為的數字,它高32位是epoch,用來標識leader關係是否改變,每次一個leader被選出來,它都會有一個新的epoch。低32位是個遞增計數。

2)版本號

對節點的每一個操作都將致使這個節點的版本號增加。每個節點維護著三個版本號,他們分別為:

① version:節點被修改的版本號

② cversion:子節點被修改的版本號

③ aversion:節點所擁有的ACL版本號

6.資料訪問

– 每個節點上的“訪問控制鏈” (ACL,AccessControl List)儲存了各客戶端對於該節點的訪問許可權。

– 用一個三元組來定義客戶端的訪問許可權:(scheme模式,expression,permissions許可權)

• ip:19.22.0.0/16,READ   表示IP地址以19.22開頭的主機有該資料節點的讀許可權。permissions許可權列表如下。

許可權 

描述
CREATE 有建立子節點的許可權
READ 有讀取節點資料和子節點列表的許可權
WRITE 有修改節點資料的許可權 無建立和刪除子節點的許可權
DELETE 有刪除子節點的許可權
ADMIN 有設定節點許可權的許可權

ZooKeeper本身提供了ACL機制,表示為scheme:id,permissions,第一個欄位表示採用哪一種機制,第二個id表示使用者,permissions表示相關許可權(如只讀,讀寫,管理等)。scheme列表如下。

模式

描述
World 它下面只有一個id, 叫anyone,world:anyone代表任何人,zookeepe中對所有人有許可權的結點就是屬於world:anyone的r
Auth 已經被認證的使用者
Digest通過username:password字串的MD5編碼認證使用者
Host匹配主機名字尾,如,host:corp.com匹配host:host1.corp.com,host:host2.corp.com,但不能匹配host:host1.store.com
IP 通過IP識別使用者,表示式格式為addr/bits
public class NewDigest {
    public static void main(String[] args) throws Exception {
        List<ACL> acls = newArrayList<ACL>();
        //新增第一個id,採用使用者名稱密碼形式
        Id id1 = new Id("digest",
        DigestAuthenticationProvider.generateDigest("admin:admin"));
        ACL acl1 = new ACL(ZooDefs.Perms.ALL,id1);
        acls.add(acl1);
        //新增第二個id,所有使用者可讀許可權
        Id id2 = new Id("world","anyone");
        ACL acl2 = new ACL(ZooDefs.Perms.READ,id2);
        acls.add(acl2);
        // zk用admin認證,建立/testZNode。
        ZooKeeper zk = newZooKeeper("host1:2181,host2:2181,host3:2181", 2000, null);
        zk.addAuthInfo("digest","admin:admin".getBytes());
        zk.create("/test","data".getBytes(), acls, CreateMode.PERSISTENT);
    }
}

程式碼分析:

先建立了兩個ACL訪問的方式(id1/id2),id1採用的是digest,即使用者密碼模式,擁有所有的許可權;id2對所有使用者提供read許可權。然後建立/test節點,資料是“data”,訪問該節點的許可權有兩種(id1/id2),型別是永久性節點。

7.關鍵程式碼

客戶端要連線 Zookeeper 伺服器可以通過建立 org.apache.zookeeper. ZooKeeper 的一個例項物件,然後呼叫這個類提供的介面來和伺服器互動。

• String create(String path, byte[] data, List<ACL> acl, CreateMode createMode)

– 建立一個給定的目錄節點 path, 並給它設定資料,CreateMode標識有四種形式的目錄節點,分別是 PERSISTENT:持久化目錄節點,這個目錄節點儲存的資料不會丟失;PERSISTENT_SEQUENTIAL:順序自動編號的目錄節點,這種目錄節點會根據當前已近存在的節點數自動加 1,然後返回給客戶端已經成功建立的目錄節點名;EPHEMERAL:臨時目錄節點,一旦建立這個節點的客戶端與伺服器埠也就是 session 超時,這種節點會被自動刪除;EPHEMERAL_SEQUENTIAL:臨時自動編號節點。

• Stat exists(String path, boolean watch)

– 判斷某個 path 是否存在,並設定是否監控這個目錄節點,這裡的 watcher 是在建立ZooKeeper 例項時指定的 watcher。

• Stat exists(String path, Watcher watcher)

– 過載方法,這裡給某個目錄節點設定特定的watcher,Watcher 在 ZooKeeper 是一個核心功能,Watcher 可以監控目錄節點的資料變化以及子目錄的變化,一旦這些狀態發生變化,伺服器就會通知所有設定在這個目錄節點上的 Watcher,從而每個客戶端都很快知道它所關注的目錄節點的狀態發生變化,而做出相應的反應。

• void delete(String path, int version)

– 刪除 path 對應的目錄節點,version為 -1 可以匹配任何版本,也就刪除了這個目錄節點所有資料。

• List<String> getChildren(String path, boolean watch)

– 獲取指定 path 下的所有子目錄節點,同樣getChildren方法也有一個過載方法可以設定特定的 watcher 監控子節點的狀態。

• Stat setData(String path, byte[] data, int version)

– 給 path 設定資料,可以指定這個資料的版本號,如果 version 為 -1 怎可以匹配任何版本。

• byte[] getData(String path, boolean watch, Stat stat)

– 獲取這個 path 對應的目錄節點儲存的資料,資料的版本等資訊可以通過 stat 來指定,同時還可以設定是否監控這個目錄節點資料的狀態。

• void addAuthInfo(String scheme, byte[] auth)

– 客戶端將自己的授權資訊提交給伺服器,伺服器將根據這個授權資訊驗證客戶端的訪問許可權。

•List<ACL> getACL(String path, Stat stat)

– 獲取某個目錄節點的訪問許可權列表

• Stat setACL(String path, List<ACL> acl, int version)

– 給某個目錄節點重新設定訪問許可權,需要注意的是 Zookeeper 中的目錄節點許可權不具有傳遞性,父目錄節點的許可權不能傳遞給子目錄節點。目錄節點 ACL 由兩部分組成:perms 和id。 Perms 有 ALL、 READ、 WRITE、 CREATE、 DELETE、 ADMIN 幾種而 id 標識了訪問目錄節點的身份列表,預設情況下有以下兩種: ANYONE_ID_UNSAFE = new Id("world","anyone") 和 AUTH_IDS =new Id("auth", "") 分別表示任何人都可以訪問和建立者擁有訪問許可權。

除了以上這些上表中列出的方法之外還有一些過載方法,如都提供了一個回撥類的過載方法以及可以設定特定 Watcher 的過載方法,具體的方法可以參考 org.apache.zookeeper. ZooKeeper 類的 API 說明。

二、zookeeper使用

1.建立目錄

在zookeeper根目錄下,建立一個目錄,用它來儲存與 ZooKeeper 伺服器有關聯的一些狀態:mkdir data。

2.配置conf/zoo.cfg 

tickTime=2000
dataDir=/usr/local/src/zookeeper-3.4.5-cdh5.7.0/data
clientPort=2181
initLimit=5
syncLimit=2
server.1=masteractive:2888:3888
server.2=slave1:2888:3888
server.3=slave2:2888:3888

值得重點注意的一點是,所有三個機器都應該開啟埠 2181、2888 和 3888。在本例中,埠 2181 由 ZooKeeper 客戶端使用,用於連線到 ZooKeeper 伺服器;埠 2888 由對等 ZooKeeper 伺服器使用,用於互相通訊;而埠 3888 用於領導者選舉。可以選擇自己喜歡的任何埠。通常建議在所有 ZooKeeper 伺服器上使用相同的埠。

【注意如果配置了三臺機器,則進行練習時就要開啟三臺機器,否則zookeeper會認為叢集不可用,將會報錯,在學習時,可以將另外兩臺機子註釋掉】

3.myid

建立一個 touch –p /usr/local/src/zookeeper-3.4.5-cdh5.7.0/data/myid 檔案。此檔案的內容將只包含一個全域性唯一的數字,作為識別碼,和server.後面的數字要對應起來。例如對於masteractive主機,myid的值是1。

4.啟動

• zkServer.sh start(每臺機器都要啟動,否則報錯)

• zkServer.sh status 檢視狀態

• zkServer.sh stop 停止zookeeper

• zkCli.sh -server masteractive:2181    連線到masteractive的2181埠,即連線zookeeper服務

 • 執行客戶端 zkCli.sh

– ls / 檢視當前目錄
– create /text "test"     建立節點,名稱為test
– create -e /text "test" 建立臨時節點
– create -s /text "test" 建立序列節點
– get /test 檢視節點
– rmr /test 刪除節點
– delete /test 刪除節點(只能刪除無子節點的節點)
– stat /test 檢視後設資料

5.watch程式設計

編寫watch,根據znode的情況觸發相應的事件。

package testZK;

import java.io.IOException;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class testZK implements Watcher {
	public ZooKeeper zk = null;
	boolean bk = true;

	public void CreateConnection(String connectString, int timeout) {
		try {
			zk = new ZooKeeper(connectString, timeout, null);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void releaseConnection() {
		try {
			this.zk.close();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void createPath(String path, String data) {
		try {
//			this.zk.create(path, data.getBytes(), Ids.OPEN_ACL_UNSAFE,
//					CreateMode.EPHEMERAL);
			this.zk.create(path, data.getBytes(), Ids.OPEN_ACL_UNSAFE,
					CreateMode.PERSISTENT);
		} catch (KeeperException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public String readData(String path) {
		String ret = null;
		try {
			ret = new String(this.zk.getData(path, false, null));
		} catch (KeeperException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return ret;
	}

	public void writeData(String path, String data) {
		try {
			this.zk.setData(path, data.getBytes(), -1);
		} catch (KeeperException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void deleteNode(String path) {
		try {
			this.zk.delete(path, -1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (KeeperException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void isExist(String path) {
		try {
			this.zk.exists(path, this);
		} catch (KeeperException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void getChildren(String path) {
		try {
			this.zk.getChildren(path, this);
		} catch (KeeperException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void getData(String path) {
		try {
			this.zk.getData(path, this, null);
		} catch (KeeperException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String zkpath = "/testzk";
		testZK test = new testZK();
		test.CreateConnection("192.168.101.101:2181", 100000);
		test.createPath(zkpath, "123"); 

		String ret = test.readData(zkpath);
		System.out.println("get data :" + ret);
				
		test.writeData(zkpath, "321");

		ret = test.readData(zkpath);
		System.out.println("get data :" + ret);

		test.createPath("/test2", "123");
		test.getData("/test2"); //這裡可以設定監控,一旦test2被操作,就會觸發process
		test.getChildren("/test2");
		test.isExist("/test2/node1");
		
		test.createPath("/test2/test3", "123");

		while (test.bk) {
			System.out.print(".");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		test.releaseConnection();
	}

	@Override
	public void process(WatchedEvent arg0) {
		// TODO Auto-generated method stub這裡編寫自己的業務實現
		System.out.println("get event" + arg0.getState() + " # "+ arg0.getType() + "\n");
		this.bk = false;
	}

}

可在process中編寫程式碼實現具體的業務。

三、應用場景

Zookeeper 從設計模式角度來看,是一個基於觀察者模式設計(watch)的分散式服務管理框架,它負責儲存和管理大家都關心的資料,然後接受觀察者的註冊,一旦這些資料的狀態發生變化,Zookeeper 就將負責通知已經在 Zookeeper 上註冊的那些觀察者做出相應的反應,從而實現叢集中類似 Master/Slave 管理模式,關於 Zookeeper 的詳細架構等內部細節可以閱讀 Zookeeper 的原始碼。下面詳細介紹這些典型的應用場景,也就是 Zookeeper 到底能幫我們解決那些問題。

1.配置管理(Configuration Management)


配置的管理在分散式應用環境中很常見,例如同一個應用系統需要多臺 PC Server 執行,但是它們執行的應用系統的某些配置項是相同的,如果要修改這些相同的配置項,那麼就必須同時修改每臺執行這個應用系統的 PC Server,這樣非常麻煩而且容易出錯。像這樣的配置資訊完全可以交給 Zookeeper 來管理,將配置資訊儲存在 Zookeeper 的某個節點中,然後將所有需要修改的應用機器監控配置資訊的狀態,一旦配置資訊發生變化,每臺應用機器就會收到 Zookeeper 的通知,然後從 Zookeeper 獲取新的配置資訊應用到系統中。

2.統一命名服務(Name Service)

分散式應用中,通常需要有一套完整的命名規則,既能夠產生唯一的名稱又便於人識別和記住,通常情況下用樹形的名稱結構是一個理想的選擇,樹形的名稱結構是一個有層次的目錄結構,既對人友好又不會重複。說到這裡你可能想到了 JNDI,沒錯 Zookeeper 的 Name Service 與 JNDI 能夠完成的功能是差不多的,它們都是將有層次的目錄結構關聯到一定資源上,但是 Zookeeper 的 Name Service 更加是廣泛意義上的關聯,也許你並不需要將名稱關聯到特定資源上,你可能只需要一個不會重複名稱,就像資料庫中產生一個唯一的數字主鍵一樣。Name Service 已經是 Zookeeper 內建的功能,你只要呼叫 Zookeeper 的 API 就能實現。如呼叫 create 介面就可以很容易建立一個目錄節點。

3.叢集管理(Group Membership)

Zookeeper 能夠很容易的實現叢集管理的功能,如有多臺 Server 組成一個服務叢集,那麼必須要一個“總管”知道當前叢集中每臺機器的服務狀態,一旦有機器不能提供服務,叢集中其它叢集必須知道,從而做出調整重新分配服務策略。同樣當增加叢集的服務能力時,就會增加一臺或多臺 Server,同樣也必須讓“總管”知道。

Zookeeper 不僅能夠幫你維護當前的叢集中機器的服務狀態,而且能夠幫你選出一個“總管”,讓這個總管來管理叢集,這就是 Zookeeper 的另一個功能 Leader Election

它們的實現方式都是在 Zookeeper 上建立一個 EPHEMERAL 型別的目錄節點,然後每個 Server 在它們建立目錄節點的父目錄節點上呼叫 getChildren(String path, boolean watch) 方法並設定 watch 為 true,由於是 EPHEMERAL 目錄節點,當建立它的 Server 死去,這個目錄節點也隨之被刪除,所以 Children 將會變化,這時 getChildren上的 Watch 將會被呼叫,所以其它 Server 就知道已經有某臺 Server 死去了。新增 Server 也是同樣的原理。

Zookeeper 如何實現 Leader Election,也就是選出一個 Master Server。和前面的一樣每臺 Server 建立一個 EPHEMERAL 目錄節點,不同的是它還是一個 SEQUENTIAL 目錄節點,所以它是個 EPHEMERAL_SEQUENTIAL 目錄節點。之所以它是 EPHEMERAL_SEQUENTIAL 目錄節點,是因為我們可以給每臺 Server 編號,我們可以選擇當前是最小編號的 Server 為 Master,假如這個最小編號的 Server 死去,由於是 EPHEMERAL 節點,死去的 Server 對應的節點也被刪除,所以當前的節點列表中又出現一個最小編號的節點,我們就選擇這個節點為當前 Master。這樣就實現了動態選擇 Master,避免了傳統意義上單 Master 容易出現單點故障的問題。


4.共享鎖

共享鎖在同一個程式中很容易實現,但是在跨程式或者在不同 Server 之間就不好實現了。Zookeeper 卻很容易實現這個功能,實現方式也是需要獲得鎖的 Server 建立一個 EPHEMERAL_SEQUENTIAL 目錄節點,然後呼叫 getChildren方法獲取當前的目錄節點列表中最小的目錄節點是不是就是自己建立的目錄節點,如果正是自己建立的,那麼它就獲得了這個鎖,如果不是那麼它就呼叫exists(String path, boolean watch) 方法並監控 Zookeeper 上目錄節點列表的變化,一直到自己建立的節點是列表中最小編號的目錄節點,從而獲得鎖,釋放鎖很簡單,只要刪除前面它自己所建立的目錄節點就行了。


5.佇列管理

Zookeeper 可以處理兩種型別的佇列:

1)當一個佇列的成員都聚齊時,這個佇列才可用,否則一直等待所有成員到達,這種是同步佇列。

2)佇列按照 FIFO 方式進行入隊和出隊操作,例如實現生產者和消費者模型。

同步佇列用 Zookeeper 實現的實現思路如下:建立一個父目錄 /synchronizing,每個成員都監控標誌(Set Watch)位目錄 /synchronizing/start 是否存在,然後每個成員都加入這個佇列,加入佇列的方式就是建立 /synchronizing/member_i 的臨時目錄節點,然後每個成員獲取 / synchronizing 目錄的所有目錄節點,也就是 member_i。判斷 i 的值是否已經是成員的個數,如果小於成員個數等待 /synchronizing/start 的出現,如果已經相等就建立 /synchronizing/start。

6.FIFO

– 建立 SEQUENTIAL 型別的子目錄 /queue_i,這樣就能保證所有成員加入佇列時都是有編號的,出佇列時通過getChildren( ) 方法可以返回當前所有的佇列中的元素,然後消費其中最小的一個,這樣就能保證 FIFO。

四、zab協議

參考:http://www.cnblogs.com/hongdada/p/8145075.html

為了保證寫操作的一致性與可用性,Zookeeper專門設計了一種名為原子廣播(ZAB)的支援崩潰恢復的一致性協議。基於該協議,Zookeeper實現了一種主從模式的系統架構來保持叢集中各個副本之間的資料一致性。根據ZAB協議,所有的寫操作都必須通過Leader完成,Leader寫入本地日誌後再複製到所有的Follower節點。一旦Leader節點無法工作,ZAB協議能夠自動從Follower節點中重新選出一個合適的替代者,即新的Leader,該過程即為領導選舉。該領導選舉過程,是ZAB協議中最為重要和複雜的過程。下面對zab進行學習。

 1.概述

ZooKeeper為高可用的一致性協調框架,自然的ZooKeeper也有著一致性演算法的實現,ZooKeeper使用的是ZAB協議作為資料一致性的演算法, ZAB(ZooKeeper Atomic Broadcast ) 全稱為:原子訊息廣播協議。

ZAB可以說是在Paxos演算法基礎上進行了擴充套件改造而來的,ZAB協議設計了支援崩潰恢復,ZooKeeper使用單一主程式Leader用於處理客戶端所有事務請求,採用ZAB協議將伺服器數狀態以事務形式廣播到所有Follower上;由於事務間可能存在著依賴關係,ZAB協議保證Leader廣播的變更序列被順序的處理:一個狀態被處理那麼它所依賴的狀態也已經提前被處理。

ZAB協議支援的崩潰恢復可以保證在Leader程式崩潰的時候可以重新選出Leader並且保證資料的完整性。在ZooKeeper中所有的事務請求都由一個主伺服器也就是Leader來處理,其他伺服器為Follower,Leader將客戶端的事務請求轉換為事務Proposal,並且將Proposal分發給叢集中其他所有的Follower,然後Leader等待Follwer反饋,當有過半數(>=N/2+1) 的Follower反饋資訊後,Leader將再次向叢集內Follower廣播Commit資訊,Commit為將之前的Proposal提交。

1)寫leader

通過Leader進行寫操作流程如下圖所示

由上圖可見,通過Leader進行寫操作,主要分為五步:
1.客戶端向Leader發起寫請求
2.Leader將寫請求以Proposal的形式發給所有Follower並等待ACK
3.Follower收到Leader的Proposal後返回ACK

4Leader得到過半數的ACK(Leader對自己預設有一個ACK)後向所有的Follower和Observer傳送Commmit
5.Leader將處理結果返回給客戶端

這裡要注意:

Leader並不需要得到Observer的ACK,即Observer無投票權。Leader不需要得到所有Follower的ACK,只要收到過半的ACK即可,同時Leader本身對自己有一個ACK。上圖中有4個Follower,只需其中兩個返回ACK即可,因為(2+1) / (4+1) > 1/2。Observer雖然無投票權,但仍須同步Leader的資料從而在處理讀請求時可以返回儘可能新的資料。

2)寫follower/observer

通過Follower/Observer進行寫操作流程:Follower/Observer均可接受寫請求,但不能直接處理,而需要將寫請求轉發給Leader處理除了多了一步請求轉發,其它流程與直接寫Leader無任何區別。

3)讀

Leader/Follower/Observer都可直接處理讀請求,從本地記憶體中讀取資料並返回給客戶端即可。

由於處理讀請求不需要伺服器之間的互動,Follower/Observer越多,整體可處理的讀請求量越大,也即讀效能越好。

2.資料一致性

ZooKeeper從以下幾點保證了資料的一致性

① 順序一致性:來自任意特定客戶端的更新都會按其傳送順序被提交。也就是說,如果一個客戶端將Znode z的值更新為a,在之後的操作中,它又將z的值更新為b,則沒有客戶端能夠在看到z的值是b之後再看到值a(如果沒有其他對z的更新)。

② 原子性:每個更新要麼成功,要麼失敗。這意味著如果一個更新失敗,則不會有客戶端會看到這個更新的結果。

③ 單一系統映像:一個客戶端無論連線到哪一臺伺服器,它看到的都是同樣的系統檢視。這意味著,如果一個客戶端在同一個會話中連線到一臺新的伺服器,它所看到的系統狀態不會比在之前伺服器上所看到的更老。當一臺伺服器出現故障,導致它的一個客戶端需要嘗試連線集合體中其他的伺服器時,所有滯後於故障伺服器的伺服器都不會接受該連線請求,除非這些伺服器趕上故障伺服器。

④ 永續性:一個更新一旦成功,其結果就會持久存在並且不會被撤銷。這表明更新不會受到伺服器故障的影響。

3.FastLeaderElection

參考:http://www.jasongj.com/zookeeper/fastleaderelection/

4.zab模式

ZAB協議的兩個基本模式:恢復模式和廣播模式

1)恢復模式:(選舉)

當服務啟動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數server完成了和leader的狀態同步以後,恢復模式就結束了。狀態同步保證了leader和server具有相同的系統狀態。崩潰恢復過程中,為了保證資料一致性需要處理特殊情況:

1、已經被leader提交的proposal確保最終被所有的伺服器follower提交

2、確保那些只在leader被提出的proposal被丟棄

針對這個要求,如果讓leader選舉演算法能夠保證新選舉出來的Leader伺服器擁有叢集中所有機器最高的ZXID事務proposal,就可以保證這個新選舉出來的Leader一定具有所有已經提交的proposal,也可以省去Leader伺服器檢查proposal的提交與丟棄的工作。

2)廣播模式:(資料同步)

一旦Leader已經和多數的Follower進行了狀態同步後,他就可以開始廣播訊息了,即進入廣播狀態。

這時候當一個Server加入ZooKeeper服務中,它會在恢復模式下啟動,發現Leader,並和Leader進行狀態同步。待到同步結束,它也參與訊息廣播。ZooKeeper服務一直維持在廣播狀態,直到Leader崩潰了或者Leader失去了大部分的Followers支援。

ZAB協議簡化了2PC事務提交:

1、去除中斷邏輯移除,follower要麼ack,要麼拋棄Leader;

2、leader不需要所有的Follower都響應成功,只要一個多數派ACK即可。

丟棄的事務proposal處理過程:ZAB協議中使用ZXID作為事務編號,ZXID為64位數字,低32位為一個遞增的計數器,每一個客戶端的一個事務請求時Leader產生新的事務後該計數器都會加1,高32位為Leader週期epoch編號,當新選舉出一個Leader節點時Leader會取出本地日誌中最大事務Proposal的ZXID解析出對應的epoch把該值加1作為新的epoch,將低32位從0開始生成新的ZXID;ZAB使用epoch來區分不同的Leader週期,能有效避免了不同的leader伺服器錯誤的使用相同的ZXID編號提出不同的事務proposal的異常情況,大大簡化了提升了資料恢復流程。所以這個崩潰的機器啟動時,也無法成為新一輪的Leader,因為當前叢集中的機器一定包含了更高的epoch的事務proposal。


相關文章