1. 程式人生 > >ZooKeeper學習筆記二

ZooKeeper學習筆記二

實戰使用

ZooKeeper框架的安裝

1. 下載並tar開解壓(略)
2. 建立配置檔案
建立conf/zoo.cfg 配置檔案

tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181

 引數說明:

  1. tickTime: zookeeper中使用的基本時間單位, 毫秒值.
  2. dataDir: 資料目錄. 可以是任意目錄.
  3. dataLogDir: log目錄, 同樣可以是任意目錄. 如果沒有設定該引數, 將使用和dataDir相同的設定.
  4. clientPort: 監聽client連線的埠號.

啟動ZooKeeper伺服器

$ bin/zkServer.sh start

進入 CLI

$ bin/zkCli.sh

停止ZooKeeper伺服器

$ bin/zkServer.sh stop

Zookeeper CLI

ZooKeeper 命令列介面(CLI)是用來與 ZooKeeper 整合作開發進行互動的。這是在除錯和使用不同的選項時的工作有用。
為了執行ZooKeeper的CLI操作, ZooKeeper伺服器首先要啟動 (“bin/zkServer.sh start”) , 然後使用 ZooKeeper 客戶端 (“bin/zkCli.sh”). 當客戶端啟動後,可以執行以下操作:(1)建立znodes,(2)獲取資料,(3)監視 znode 變化,(4)設定資料,(5)建立 znode 的子 znode,(6)列出一個 znode 的子 znode,(7)檢查狀態,(8)刪除一個 znode

  • 建立Znodes:create /path /data
  • 獲取資料:get /path
  • 監視:get /path [watch] 1
  • 設定資料:set /path /data
  • 建立子znode:create /parent/path/subnode/path /data
  • 列出子znode:ls /path
  • 檢查狀態:stat /path
  • 刪除Znode:rmr /path

Java API使用

導包

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.13</version>
</dependency>

常用方法列表

String create(final String path, byte data[], List acl, CreateMode createMode)

引數: 路徑、 znode內容,ACL(訪問控制列表)、 znode建立型別;

用途:建立znode節點

void delete(final String path, int version)

引數: 路徑、版本號;如果版本號與znode的版本號不一致,將無法刪除,是一種樂觀加鎖機制;如果將版本號設定為-1,不會去檢測版本,直接刪除;

用途:刪除節點

Stat exists(final String path, Watcher watcher)

引數: 路徑、Watcher(監視器);當這個znode節點被改變時,將會觸發當前Watcher

用途:判斷znode節點是否存在

Stat exists(String path, boolean watch)

引數: 路徑、並設定是否監控這個目錄節點,這裡的 watcher 是在建立 ZooKeeper 例項時指定的 watcher;

判斷znode節點是否存在

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

引數: 路徑、資料、版本號;如果為-1,跳過版本檢查

用途:設定znode上的資料

byte[] getData(final String path, Watcher watcher, Stat stat)

引數: 路徑、監視器、資料版本等資訊

用途:獲取znode上的資料

List getChildren(final String path, Watcher watcher)

引數: 路徑、監視器;該方法有多個過載

用途:獲取節點下的所有子節點

Zookeeper 常用API

ZooKeeper有一個Java和C繫結的官方API。ZooKeeper社群提供了對於大多數語言(.NET,Python等)的非官方API。使用ZooKeeper的API,應用程式可以連線,互動,操作資料,協調,以及從ZooKeeper整合斷開。

連線

原始碼

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
    throws IOException

原始碼解析

  1. connectString:zookeeper服務地址,例如“192.168.117.128:2181”
  2. sessionTimeout :超時時間,單位為毫秒
  3. watcher:實現org.apache.zookeeper.Watcher介面的實現類,需實現process(WatchedEvent watchedEvent) 方法

例項

/**
 * 連線zk伺服器並建立zk例項
 *
 * @throws InterruptedException
 */
public static void connect() throws InterruptedException {
    final CountDownLatch connectedLatch = new CountDownLatch(1);
    try {
        zooKeeper = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            public void process(WatchedEvent event) {
                if  (event.getState()  ==  Event.KeeperState.SyncConnected) {
                    connectedLatch.countDown();
                }
            }
        });
        System.out.println("伺服器連線成功");
    } catch (IOException e) {
        System.out.println("伺服器連線失敗");
        e.printStackTrace();
    }
    connectedLatch.await();
}

Create:建立同步節點

原始碼

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

原始碼解析

  1. path:建立節點路徑,需保證父節點已存在
  2. data:節點資料
  3. acl:(Access Control List)許可權列表
  4. createMode:節點型別

acl許可權列表

使用api 中預設的ACL
在建立znode時可以設定該znode的ACL列表。介面org.apache.zookeeper.ZooDefs.Ids中有一些已經設定好的許可權常量,例如:

  1. OPEN_ACL_UNSAFE:完全開放
  2. CREATOR_ALL_ACL:建立該znode的連線擁有所有許可權
  3. READ_ACL_UNSAFE:所有的客戶端都可讀

createMode:節點型別

  1. CreateMode.PERSISTENT:持久化節點
  2. CreateMode.EPHEMERAL:臨時節點(連線斷開自動刪除)
  3. CreateMode.PERSISTENT_SEQUENTIAL:持久化有序節點
  4. CreateMode.EPHEMERAL_SEQUENTIAL:臨時有序節點(連線斷開自動刪除)

例項

/**
 * 建立節點
 *
 * @param node
 * @param data
 */
public void create(String node, String data) {
    //建立
    String result = null;
    try {
        result = zooKeeper.create(
                node,
                data.getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
        System.out.println("返回的路徑為:" + result);
    } catch (KeeperException e) {
        System.out.println("建立失敗,節點已存在");
        e.printStackTrace();
    } catch (InterruptedException e) {
        System.out.println("建立失敗:" + "the transaction is interrupted");
        e.printStackTrace();
    }
    System.out.println("建立成功:" + result);
}

getData:獲取節點

原始碼

public byte[] getData(final String path, Watcher watcher, Stat stat)

原始碼解析

znode節點的狀態資訊

使用get命令獲取指定節點的資料時, 同時也將返回該節點的狀態資訊, 稱為Stat. 其包含如下欄位:

  • czxid. 節點建立時的zxid.
  • mzxid. 節點最新一次更新發生時的zxid.
  • ctime. 節點建立時的時間戳.
  • mtime. 節點最新一次更新發生時的時間戳.
  • dataVersion. 節點資料的更新次數.
  • cversion. 其子節點的更新次數.
  • aclVersion. 節點ACL(授權資訊)的更新次數.
  • ephemeralOwner. 如果該節點為ephemeral節點, ephemeralOwner值表示與該節點繫結的session id. 如果該節點不是ephemeral節點, ephemeralOwner值為0. 至於什麼是ephemeral節點, 請看後面的講述.
  • dataLength. 節點資料的位元組數.
  • numChildren. 子節點個數.

例項

/**
 * 讀取節點
 *
 * @param node
 */
public void getData(String node) {
    Stat stat = new Stat();
    try {
        byte[] bytes = zooKeeper.getData(node, watcher, stat);
        System.out.println("資料存在" + bytes.toString());
        System.out.println("stat:" + stat);
    } catch (KeeperException e) {
        System.out.println("資料不存在");
        e.printStackTrace();
    } catch (InterruptedException e) {
        System.out.println("the server transaction is interrupted");
        e.printStackTrace();
    }
}

獲取子節點

例項

/**
 * 輸出子節點
 * @param node
 */
public void getChildren(String node) {
    try {
        List<String> zooKeeperChildren = zooKeeper.getChildren(node, watcher);
        System.out.println(node + "的子節點共有:" +
                zooKeeperChildren.size() + "個"
        );
        System.out.print("[");
        for (String child : zooKeeperChildren) {
            System.out.print(child + ",");
        }
        System.out.println("]");
    } catch (KeeperException e) {
        System.out.println("節點不存在");
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

注:getChildren方法會在子點有變化時觸發watcher 這個監聽器

刪除節點

/**
 * 刪除節點
 *
 * @param node
 */
public void deleteNode(String node) {
    // 刪除
    try {
        zooKeeper.delete(node, -1);
        System.out.println("刪除節點成功:" + node);
    } catch (KeeperException e) {
        System.out.println("節點不存在");
        e.printStackTrace();
    } catch (InterruptedException e) {
        System.out.println("刪除節點失敗:the server transaction is interrupted");
        e.printStackTrace();
    }
}

關閉連連線

/**
 * 關閉zk連線
 */
public void close() {
    try {
        zooKeeper.close();
    } catch (InterruptedException e) {
        System.out.println("關閉失敗");
        e.printStackTrace();
    }
}

注:一般情況下close()方法放在finally程式碼塊執行

常見異常

InterruptedException 異常

@throws InterruptedException if the transaction is interrupted

若客戶端的某操作被中斷,則會丟擲InterruptedException異常。丟擲該異常時,不一定是出現故障,只能表明某個zookeeper操作被中斷而已。

KeeperException 異常

@throws KeeperException if the server returns a non-zero error code

伺服器發出錯誤訊號或是伺服器存在通訊故障。該類現在共有21個子類, 分為3大類:

關於KeeperErrorCode = ConnectionLoss錯誤

在使用Zookeeper API時,常常會引發以下錯誤 ,這是由於連線還未完成就執行zookeeper的get/create/exists操作引起的

Exception in thread "main" org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /test
at org.apache.zookeeper.KeeperException.create(KeeperException.Java:99)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.exists(ZooKeeper.java:1501)
at org.apache.zookeeper.ZooKeeper.exists(ZooKeeper.java:1529)
at com.jiq.test.ZooKeeperTest.main(ZooKeeperTest.java:12)

解決的辦法是等Zookeeper連線初始化完成再使用例項,以下是示例程式碼

public static ZooKeeper getInstance() throws IOException, InterruptedException {
//--------------------------------------------------------------
// 為避免連線還未完成就執行zookeeper的get/create/exists操作引起的(KeeperErrorCode = ConnectionLoss)
// 這裡等Zookeeper的連線完成才返回例項
//--------------------------------------------------------------
    ZooKeeper zk = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
        @Override
        public void process(WatchedEvent event) {
            if (event.getState() == Event.KeeperState.SyncConnected) {
                connectedSignal.countDown();
            }
        }
    });
    connectedSignal.await();
    return zk;
}

注:Zookeeper的監控只有在使用getData(),exists(),getChildren()這幾個方法時才會觸發watcher

常見問題

Zk基礎知識總結

  1. zookeeper是一個開源的分散式協調服務框架。
  2. 應用場景:分散式通知/協調、負載均衡、配置中心、分散式鎖、分散式佇列等。
  3. 使用ZAB協議,Paxos演算法。
  4. 節點型別:持久節點、持久順序節點、臨時節點、臨時順序節點。
  5. 不是永久的,一次性的,需要藉助第三方工具實現重複註冊。
  6. 部署模式:單機模式、偽叢集模式、叢集模式。
  7. 叢集角色:leader、follower、observer。
  8. 叢集規則為2N+1臺,N>0,即3臺。
  9. 叢集需要一半以上的機器可用,所以,3臺掛掉1臺還能工作,2臺不能。
  10. 3.5版本開始支援動態擴容。
  11. java客戶端:zk自帶的zkclient及Apache開源的Curator。
  12. chubby是google的,完全實現paxos演算法,不開源。zookeeper是chubby的開源實現,使用zab協議,paxos演算法的變種。
  13. 常用命令:ls get set create delete等。

1.zookeeper是如何保證事務的順序一致性的

zookeeper採用了遞增的事務Id來標識,所有的proposal都在被提出的時候加上了zxid,zxid實際上是一個64位的數字,高32位是epoch用來標識leader是否發生改變,如果有新的leader產生出來,epoch會自增,低32位用來遞增計數。

當新產生proposal的時候,會依據資料庫的兩階段過程,首先會向其他的server發出事務執行請求,如果超過半數的機器都能執行並且能夠成功,那麼就會開始執行

2.zookeeper是如何選取主leader的?

當leader崩潰或者leader失去大多數的follower,這時zk進入恢復模式,通過節點間廣播訊息,推舉出資料最新的節點,成為 Leader。

3.ZNode 的分類

zk中znode型別有四種:

  • 持久化節點
  • 持久化順序節點(有順序 能夠在註冊機器等許多場景用到)
  • 臨時節點
  • 臨時順序節點

4.zk的通知機制

client端會對某個znode建立一個watcher事件,當該znode發生變化時,這些client會收到zk的通知,然後client可以根據znode變化來做出業務上的改變等。

5.zk的配置管理

程式分散式的部署在不同的機器上,將程式的配置資訊放在zk的znode下,當有配置發生改變時,也就是znode發生變化時,可以通過改變zk中某個目錄節點的內容,利用water通知給各個客戶端 從而更改配置。

6.zk的命名服務

命名服務是指通過指定的名字來獲取資源或者服務的地址,利用zk建立一個全域性的路徑,這個路徑就可以作為一個名字,指向叢集中的叢集,提供的服務的地址,或者一個遠端的物件等等。

7.分散式通知和協調

對於系統排程來說:操作人員傳送通知實際是通過控制檯改變某個節點的狀態,然後zk將這些變化傳送給註冊了這個節點的watcher的所有客戶端。

對於執行情況彙報:每個工作程序都在某個目錄下建立一個臨時節點。並攜帶工作的進度資料,這樣彙總的程序可以監控目錄子節點的變化獲得工作進度的實時的全域性情況。

8.機器中為什麼會有master

在分散式環境中,有些業務邏輯只需要叢集中的某一臺機器進行執行,其他的機器可以共享這個結果,這樣可以大大減少重複計算,提高效能,於是就需要進行master選舉。

2. 分散式協調zookeeper的如何進行領導選舉的?

(1)選舉原理圖:

(2)具體解釋如下:

1)每個Server啟動以後都詢問其它的Server它要投票給誰。

2)對於其他server的詢問,server每次根據自己的狀態都回復自己推薦的leader的id和上一次處理事務的zxid(系統啟動時每個server都會推薦自己)

3)收到所有Server回覆以後,就計算出zxid最大的哪個Server,並將這個Server相關資訊設定成下一次要投票的Server。

4)計算這過程中獲得票數最多的的sever為獲勝者,如果獲勝者的票數超過半數,則該server被選為leader。否則,繼續這個過程,直到leader被選舉出來。

Dubbo中zookeeper做註冊中心,如果註冊中心叢集都掛掉,釋出者和訂閱者之間還能通訊麼?

可以。因為啟動duboo時,消費者會從zookeeper獲取註冊的生產者的地址介面等資料,快取在本地。每次呼叫時,按照本地儲存的地址進行呼叫。

另外,dubbo在健壯性的文件中也有描述:

1)註冊中心對等叢集,任意一臺宕機後,將自動切換懂啊另一臺

2)註冊中心全部宕機後,服務提供者和服務消費者仍能通過本地快取通訊

zk的通知機制

client端會對某個znode建立一個watcher事件,當該znode發生變化時,這些client會收到zk的通知,然後client可以根據znode變化來做出業務上的改變等。

zk的配置管理

程式分散式的部署在不同的機器上,將程式的配置資訊放在zk的znode下,當有配置發生改變時,也就是znode發生變化時,可以通過改變zk中某個目錄節點的內容,利用water通知給各個客戶端 從而更改配置。

zk的命名服務

命名服務是指通過指定的名字來獲取資源或者服務的地址,利用zk建立一個全域性的路徑,這個路徑就可以作為一個名字,指向叢集中的叢集,提供的服務的地址,或者一個遠端的物件等等。

分散式通知和協調

對於系統排程來說:操作人員傳送通知實際是通過控制檯改變某個節點的狀態,然後zk將這些變化傳送給註冊了這個節點的watcher的所有客戶端。
對於執行情況彙報:每個工作程序都在某個目錄下建立一個臨時節點。並攜帶工作的進度資料,這樣彙總的程序可以監控目錄子節點的變化獲得工作進度的實時的全域性情況。

機器中為什麼會有master;
在分散式環境中,有些業務邏輯只需要叢集中的某一臺機器進行執行,其他的機器可以共享這個結果,這樣可以大大減少重複計算,提高效能,於是就需要進行master選舉。

參考:

https://www.cnblogs.com/shengkejava/p/5611671.html

https://www.cnblogs.com/leocook/p/zk_1.html

https://www.zhihu.com/question/35139415

http://ningg.top/zookeeper-lesson-11-zookeeper-application/

http://www.cnblogs.com/leesf456/p/6036548.html

https://www.cnblogs.com/lanqiu5ge/p/9405601.html

https://mp.weixin.qq.com/s/AE_2U5tUjEZSYoLovlcU6g

https://www.w3cschool.cn/zookeeper/zookeeper_fundamentals.html

https://www.cnblogs.com/raphael5200/p/5285583.html