天天看點

Hadoop 之 ZooKeeper (一)

Hadoop 之 ZooKeeper

本文介紹使用 Hadoop 的分布式協調服務建構通用的分布式應用 —— ZooKeeper。 ZooKeeper 是 Hadoop 分布式協調服務。

寫分布式應用是比較難的,主要是因為部分失敗(partial failure). 當一條消息通過網絡在兩個節點間發送時,如果發生網絡錯誤,發送者無法知道接受者

是否接收到了這條消息。接收者可能在發生網絡錯誤之前已經收到了這條消息,也可能沒有收到,又或者接收者的程序已經死掉。發送者能獲知到底發生了什

麼事的唯一途徑是重新連接配接接收者,并詢問它。這就是部分失敗:我們甚至不知道一個操作是否失敗了。

因為部分失敗是分布式系統的固有特征,是以 ZooKeeper 并不能避免部分失敗,當然它也不能隐藏部分失敗。但 ZooKeeper 為我們提供了一系列工具來建構

分布式應用來安全地處理部分失敗。

    Apache ZooKeeper

    -------------------------------------------------------------------------------------------------------------------------------------

    ZooKeeper 是 Apache 軟體基金會旗下的一個獨立開源系統,它是 Google 公司為了解決 BigTable 中問題而提出的 Chubby 算法的開源實作。它提供了

    類似檔案系統的通路目錄和檔案(稱為 znode)的功能。通常分布式系統利用它協調所有權、注冊服務、監聽更新。

ZooKeeper 具有如下特征:

    ● ZooKeeper 是簡單的: ZooKeeper 核心是一個精簡的檔案系統,對外提供一些簡單的操作和一些額外的抽象,例如排序和通知。

    ● ZooKeeper 是富有表現力的 :ZooKeeper 原生元件 (ZooKeeper primitives) 是一組構件塊(building blocks),可用于建構各種類型資料結構協調和

    協定。相關例子包括分布式隊列,分布式鎖,以及一組節點中的上司者選舉(leader election).

    ● ZooKeeper 是高可用的:ZooKeeper 運作于一組機器之上,并設計為高可用性,是以應用程式可以依賴于它。ZooKeeper 可以幫助系統避免出現單點故障,

    是以可以建構一個可靠的應用。

    ● ZooKeeper 采用松耦合的互動方式:ZooKeeper 互動支援的互動過程中,參與者之間不需要彼此了解。例如, ZooKeeper 可被用作一個彙集機制,這樣

    程序在不知道對方存在(或網絡狀況)的情況下能夠彼此發現并進行資訊互動。參與的各方甚至可以不必同時存在,因為一個程序可以在 ZooKeeper 中留下

    一條消息,而另外一個程序即使在之前的那個程序退出後也可以讀取這條消息。

    ● ZooKeeper 是一個資源庫 (library) : ZooKeeper 提供了一個開源的,共享的通用協調模式實作倉庫。使個人程式員免于自己編寫這類通用協定(通常很

    難寫好)。随着時間的推移,ZooKeeper 社群不斷地增加和改進資源庫,每個人都會從中受益。

ZooKeeper 也是高性能的。在 Yahoo! , ZooKeeper 誕生的地方,對于由幾百個用戶端産生的以寫操作為主的工作負載來說,ZooKeeper 叢集基準測試吞吐量

已經超過每秒 10000 個操作;對于以讀操作為主的工作負載來說,吞吐量更是高出好幾倍。

1. 安裝和運作 ZooKeeper (Installing and Running ZooKeeper)

-----------------------------------------------------------------------------------------------------------------------------------------

首次嘗試使用 ZooKeeper 時,最簡單的方法是在一台 ZooKeeper 伺服器上以獨立模式(standalone mode)運作。可以在一台用于開發的機器上嘗試運作。

ZooKeeper 要求 Java 運作,是以首先要確定已經安裝好 Java .

下載下傳 :http://zookeeper.apache.org/releases.html

解包 :

    % tar xzf zookeeper-x.y.z.tar.gz

設定環境變量:

    % export ZOOKEEPER_HOME=~/sw/zookeeper-x.y.z

    % export PATH=$PATH:$ZOOKEEPER_HOME/bin

配置檔案:conf/zoo.cfg,   也可儲存到 /etc/zookeeper 子目錄中, 如果設定環境變量 ZOOCFGDIR, 也可以設定到該關鍵變量指定的目錄内。

    cd conf

    cp zoo_sample.cfg zoo.cfg

    vi conf/zoo.cfg

最低配置:

    tickTime=2000

    dataDir=/Users/tom/zookeeper

    clientPort=2181

運作:

    % zkServer.sh start

測試:

    % echo ruok | nc localhost 2181

    imok

    ZooKeeper commands: the four-letter words

    +===============+===========+===========================================================================

    | 分類            | Command    | 描述

    +---------------+-----------+---------------------------------------------------------------------------

    | Server status    | ruok        | Prints imok if the server is running and not in an error state.

    +---------------+-----------+---------------------------------------------------------------------------

    |                | conf        | Prints the server configuration (from zoo.cfg).

    +---------------+-----------+---------------------------------------------------------------------------

    |                | envi        | Prints the server environment, including ZooKeeper version, Java version,

    |                |            | and other system properties.

    +---------------+-----------+---------------------------------------------------------------------------

    |                | srvr        | Prints server statistics, including latency statistics, the number of

    |                |            | znodes, and the server mode (standalone, leader, or follower).

    +---------------+-----------+---------------------------------------------------------------------------

    |                | stat        | Prints server statistics and connected clients.

    +---------------+-----------+---------------------------------------------------------------------------

    |                | srst        | Resets server statistics.

    +---------------+-----------+---------------------------------------------------------------------------

    |                | isro        | Shows whether the server is in read-only (ro) mode (due to a network

    |                |            | partition) or read/write mode (rw).

    +---------------+-----------+---------------------------------------------------------------------------

    | Client         | dump        | Lists all the sessions and ephemeral znodes for the ensemble. You must

    | connections    |            | connect to the leader (see srvr) for this command.

    +---------------+-----------+---------------------------------------------------------------------------

    |                | cons        | Lists connection statistics for all the server’s clients.

    +---------------+-----------+---------------------------------------------------------------------------

    |                | crst        | Resets connection statistics.

    +---------------+-----------+---------------------------------------------------------------------------

    | Watches        | wchs        | Lists summary information for the server’s watches.

    +---------------+-----------+---------------------------------------------------------------------------

    |                | wchc        | Lists all the server’s watches by connection. Caution: may impact server

    |                |            | performance for a large number of watches.

    +---------------+-----------+---------------------------------------------------------------------------

    |                | wchp        | Lists all the server’s watches by znode path. Caution: may impact server

    |                |            | performance for a large number of watches.

    +---------------+-----------+---------------------------------------------------------------------------

    | Monitoring    | mntr        | Lists server statistics in Java properties format, suitable as a source

    |                |            | for monitoring systems such as Ganglia and Nagios.

    +---------------+-----------+---------------------------------------------------------------------------

除了 mntr 指令外,ZooKeeper 還可通過 JMX 顯示統計資訊,更詳細的内容檢視 ZooKeeper 文檔。src/contrib 目錄内還有一些監視工具。

從 ZooKeeper v3.5.0 之後,有一個内置的 web server 提供與 four-letter 指令相同資訊。通路 http://localhost:8080/commands 獲得指令清單。

2. 案例 (An Example)

-----------------------------------------------------------------------------------------------------------------------------------------

假設有一組伺服器用于為用戶端提供某種服務。我們希望每個用戶端都能找到其中的一台伺服器,這樣就可以使用這項服務。在這個例子中,其中一個挑戰

是維護這組伺服器成員清單。

這組伺服器的成員關系清單顯然不能存儲在網絡中的單個節點上,因為那個節點故障将意味着整個系統失效(我們希望整個成員清單是高度可用的)。我們先

假設已經有了一種可靠的方法來解決清單的存儲問題。仍然存在的問題是,如果其中一台伺服器出現故障,如何從清單中移除這台伺服器。某些程序需要負責

移除失效的伺服器,但注意不能是故障伺服器本身,因為它已不再運作。

我們所描述的不是一個被動的分布式資料結構,而是一個主動的,并且能夠在某些外部事件發生時可以修改資料項狀态的資料結構。 ZooKeeper 提供了這種

服務,接下來讓我們看看如何使用它來實作組成員管理應用。

2.1 ZooKeeper 中的組成員關系 (Group Membership in ZooKeeper)

-----------------------------------------------------------------------------------------------------------------------------------------

了解 ZooKeeper 的一種方法是将他了解為一個提供高可用性的檔案系統。它沒有檔案和目錄,而是統一使用節點(node)概念,稱為 znode . znode 即可以

作為儲存資料的容忍(如同檔案),也可以作為儲存其他 znode 的容器(如同目錄)。所有 znode 構成了一個階層化的命名空間,一個自然的建構成員清單的

方法是使用組名稱建立一個父 znode, 使用組成員名稱(伺服器名稱)作為節點名稱建立子 znodes . 如下圖所示:

                                +---------------+

                                |                |

                                |        /        |

                                |                |

                                +---------------+

                                +---------------+

                                |                |

                                |    /zoo        |

                                |                |

                                +---------------+

            +---------------+    +---------------+    +---------------+

            |                |    |                |    |                |

            |    /zoo/duck    |    |    /zoo/goat    |    |    /zoo/cow    |

            |                |    |                |    |                |

            +---------------+    +---------------+    +---------------+

這個示例中,沒有在任何 znode 節點中存儲資料,但在實際的應用中,可以想象将成員相關的資料存儲在它們的 znode 中,例如主機名。

2.2 建立組 (Creating the Group)

-----------------------------------------------------------------------------------------------------------------------------------------

使用 ZooKeeper 的 Java API, 為組建立節點,名稱為 /zoo

//A program to create a znode representing a group in ZooKeeper

public class CreateGroup implements Watcher {

    private static final int SESSION_TIMEOUT = 5000;

    private ZooKeeper zk;

    private CountDownLatch connectedSignal = new CountDownLatch(1);

    public void connect(String hosts) throws IOException, InterruptedException {

        zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);

        connectedSignal.await();

    }

    @Override

    public void process(WatchedEvent event) { // Watcher interface

        if (event.getState() == KeeperState.SyncConnected) {

            connectedSignal.countDown();

        }

    }

    public void create(String groupName) throws KeeperException,

    InterruptedException {

        String path = "/" + groupName;

        String createdPath = zk.create(path, null, Ids.OPEN_ACL_UNSAFE,

        CreateMode.PERSISTENT);

        System.out.println("Created " + createdPath);

    }

    public void close() throws InterruptedException {

        zk.close();

    }

    public static void main(String[] args) throws Exception {

        CreateGroup createGroup = new CreateGroup();

        createGroup.connect(args[0]);

        createGroup.create(args[1]);

        createGroup.close();

    }

}

運作:

    % export CLASSPATH=ch21-zk/target/classes/:$ZOOKEEPER_HOME, Ids.OPEN_ACL_UNSAFE,

        CreateMode.EPHEMERAL);

        System.out.println("Created " + createdPath);

    }

    public static void main(String[] args) throws Exception {

        JoinGroup joinGroup = new JoinGroup();

        joinGroup.connect(args[0]);

        joinGroup.join(args[1], args[2]);

        // stay alive until process is killed or thread is interrupted

        Thread.sleep(Long.MAX_VALUE);

    }

}

//A helper class that waits for the ZooKeeper connection to be established

public class ConnectionWatcher implements Watcher {

    private static final int SESSION_TIMEOUT = 5000;

    protected ZooKeeper zk;

    private CountDownLatch connectedSignal = new CountDownLatch(1);

    public void connect(String hosts) throws IOException, InterruptedException {

        zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);

        connectedSignal.await();

    }

    @Override

    public void process(WatchedEvent event) {

        if (event.getState() == KeeperState.SyncConnected) {

            connectedSignal.countDown();

        }

    }

    public void close() throws InterruptedException {

        zk.close();

    }

}

2.4 列出組成員 (Listing Members in a Group)

-----------------------------------------------------------------------------------------------------------------------------------------

//A program to list the members in a group

public class ListGroup extends ConnectionWatcher {

    public void list(String groupName) throws KeeperException,

        InterruptedException {

        String path = "/" + groupName;

        try {

            List<String> children = zk.getChildren(path, false);

            if (children.isEmpty()) {

            System.out.printf("No members in group %s\n", groupName);

            System.exit(1);

            }

            for (String child : children) {

                System.out.println(child);

            }

        } catch (KeeperException.NoNodeException e) {

            System.out.printf("Group %s does not exist\n", groupName);

            System.exit(1);

        }

    }

    public static void main(String[] args) throws Exception {

        ListGroup listGroup = new ListGroup();

        listGroup.connect(args[0]);

        listGroup.list(args[1]);

        listGroup.close();

    }

}

運作:

    % java ListGroup localhost zoo

    No members in group zoo

    % java JoinGroup localhost zoo duck &

    % java JoinGroup localhost zoo cow &

    % java JoinGroup localhost zoo goat &

    % goat_pid=$!

    % java ListGroup localhost zoo

    goat

    duck

    cow

    % kill $goat_pid

    % java ListGroup localhost zoo

    duck

    cow

    ZooKeeper 指令行工具

    -----------------------------------------------------------------------------

    % zkCli.sh -server localhost ls /zoo

    [cow, duck]

2.5 删除組 (Deleting a Group)

-----------------------------------------------------------------------------------------------------------------------------------------

//A program to delete a group and its members

public class DeleteGroup extends ConnectionWatcher {

    public void delete(String groupName) throws KeeperException,

    InterruptedException {

    String path = "/" + groupName;

    try {

            List<String> children = zk.getChildren(path, false);

            for (String child : children) {

                    zk.delete(path + "/" + child, -1);

            }

            zk.delete(path, -1);

        } catch (KeeperException.NoNodeException e) {

            System.out.printf("Group %s does not exist\n", groupName);

            System.exit(1);

        }

    }

    public static void main(String[] args) throws Exception {

        DeleteGroup deleteGroup = new DeleteGroup();

        deleteGroup.connect(args[0]);

        deleteGroup.delete(args[1]);

        deleteGroup.close();

    }

}

運作:

% java DeleteGroup localhost zoo

% java ListGroup localhost zoo

Group zoo does not exist

3. ZooKeeper 服務 (The ZooKeeper Service)

-----------------------------------------------------------------------------------------------------------------------------------------

ZooKeeper 是高可用的,高性能的協調服務。本節,我們将從三個方面了解這個服務:模型、操作和實作

3.1 資料模型 (Data Model)

-----------------------------------------------------------------------------------------------------------------------------------------

ZooKeeper 維護一個樹形層次結構,樹中的節點稱為 znode. znode 可以用于存儲資料,并且有一個與之關聯的 ACL. ZooKeeper 設計用于實作協調服務(這

類服務通常使用小資料檔案), 而不是用于大容量資料存儲,是以一個 znode 能存儲的資料被限制在 1 MB 以内。

ZooKeeper 的資料通路是具有原子性。用戶端讀取存儲在一個 znode 中的資料永遠不會直接收到一部分資料;要麼擷取所有資料,要麼讀取失敗。類似地,

一次寫入會替換掉一個 znode 相關的所有資料。ZooKeeper 保證寫入或者成功或者失敗;不會出現部分寫入狀況。ZooKeeper 不支援追加操作。這個特征與

HDFS 不同,HDFS 設計用于大規模資料存儲,支援流式資料通路和追加操作。

ZooKeeper 通過路徑引用,路徑在 ZooKeeper 中表示為斜線分隔的(slash-delimited) Unicode 字元串, 類似 Unix 中檔案系統路徑。路徑必須是絕對的,

是以,它們必須以 / 字元開始。此外,所有的路徑表示必須是規範的,即每個路徑隻有唯一的一種表示方式,不支援路徑解析。例如, 在 Unix 中,一個具

有路徑 /a/b 的檔案可以通過路徑 /a/./b 表示,原因在于 . 在 Unix 路徑中表示為目前目錄(.. 表示為目前目錄的上一級目錄). 在 ZooKeeper 中,"."

不具有這種特殊含義,這樣表示的路徑名是不合法的。

在 ZooKeeper 中,路徑是由 Unicode 字元串構成,并且有一些限制。字元串 "zookeeper" 是一個保留詞,不能将它作為路徑表示中的一部分。需要特别指出

的是,ZooKeeper 使用 /zookeeper 子樹來儲存管理資訊,例如,關于配額的資訊。

注意, ZooKeeper 的路徑與 URI 不同,前者在 Java API 中通過 java.lang.String 來使用,而後者是通過 Hadoop Path 類(或 java.net.URI 類)使用。

znode 有一些屬性對分布式應用非常有用,下面幾節分别讨論。

    ■ 短暫 znodes(Ephemeral znodes)

    -----------------------------------------------------------------------------------------------------------------------------------------

    正如所看到的,znode 可以是兩種類型之一:短暫的(ephemeral)或持久的(persistent)。一個 znode 的類型在建立時設定并且之後不可改變。一個短暫的

    znode 在建立它的用戶端會話結束時由 ZooKeeper 删除。相反,持久類型的 znode 不與用戶端會話關聯,并且隻有當一個用戶端(不一定是建立它的那個客

    戶端)明确删除時它才被删除。短暫 znode 沒有子 znode,即便是短暫 znode 也沒有.

    雖然短暫 znode 綁定到一個用戶端會話(client session), 但它們對所有用戶端是可見的(當然受 ACL 政策控制).

    短暫 znode 對于建構需要知道特定時刻有哪些分布式資源可用的應用來說,是一種理想的選擇。本章之前的案例使用短暫 znode 實作組成員服務,是以,

    任何程序在任何特定時間都能發現哪些組成員可用。

    ■ 順序号(Sequence numbers)

    -----------------------------------------------------------------------------------------------------------------------------------------

    順序的 znode (sequential znode) 由 ZooKeeper 給出一個順序号(a sequence number)作為其名稱的一部分。如果一個 znode 建立時設定了順序标志(

    sequential flag), 那麼一個單步遞增的計數器(由父節點維護)就會追加到它名稱之後。

    例如,如果一個用戶端請求使用名稱 /a/b- 建立一個順序的 znode, 建立的 znode 實際名稱可能為 /a/b-3. 之後,如果另一個順序 znode 節點使用 /a/b-

    建立了,它會使用一個更大的計數器值給定一個唯一的名稱,例如 /a/b-5. 在 Java API 中,給順序 znode 的實際路徑是作為調用 create() 的傳回值傳回

    給用戶端的。

    順序号可用于分布式系統發生事件時進行全局排序,并用于用戶端推斷事件順序。

    ■ 觀察(Watches)

    -----------------------------------------------------------------------------------------------------------------------------------------

    當一個 znode 以某種方式發生變化時,觀察允許用戶端得到通知。觀察由 ZooKeeper 服務上的操作設定,并由服務上的其他操作觸發。例如,一個用戶端

    可以在一個 znode 上調用 exists 操作,同時将一個 watch 置于其上。如果這個 znode 不存在了, exists 操作會傳回 false. 如果,在之後的某個時間,

    這個 znode 由另一用戶端建立了,這個 watch 被觸發,通知前一個用戶端這個 znode 的建立。

    Watchers 隻被觸發一次。要接收多個通知,用戶端需要重新注冊這個 watch. 是以,如果前一個例子中的用戶端希望接收這個 znode 的存在與否的更多通知

    (例如,被删除時的通知), 它需要再次調用 exists 操作來設定一個新的 watch.

3.2 操作 (Operations)

-----------------------------------------------------------------------------------------------------------------------------------------

ZooKeeper 中有 9 種基本操作,如下表所示:

            Operations in the ZooKeeper service

    +===================+==============================================================

    |    操作            |    描述

    +-------------------+--------------------------------------------------------------

    | create            | Creates a znode (the parent znode must already exist)

    +-------------------+---------------------------------------------------------------

    | delete            | Deletes a znode (the znode must not have any children)

    +-------------------+--------------------------------------------------------------

    | exists            | Tests whether a znode exists and retrieves its metadata

    +-------------------+---------------------------------------------------------------

    | getACL, setACL    | Gets/sets the ACL for a znode

    +-------------------+--------------------------------------------------------------

    | getChildren        | Gets a list of the children of a znode

    +-------------------+--------------------------------------------------------------

    | getData, setData    | Gets/sets the data associated with a znode

    +-------------------+-------------------------------------------------------------

    | sync                | Synchronizes a client’s view of a znode with ZooKeeper

    +-------------------+---------------------------------------------------------------

ZooKeeper 中的更新操作是有條件的(are conditional). 一次 delete 或 setData 操作必須指定要更新的 znode 的版本号(可以通過調用 exists 獲得).

如果版本号不比對,更新會失敗。更新是非阻塞操作,是以,用戶端更新失敗(由于其他程序同時在更新該 znode)可以決定是否再次更新或執行一些其他操作,

不會是以而阻塞其他程序的執行進度。

雖然 ZooKeeper 可以被被看作是一個檔案系統,但處于簡單性的需要,一些檔案系統的基本操作被它擯棄了,因為 ZooKeeper 檔案很小,并且整體寫入或整體

讀出,沒有必要提供 open, close, or seek 這些操作。    

    ■ 批次更新 (Multiupdate)

    -----------------------------------------------------------------------------------------------------------------------------------------

    有另一個 ZooKeeper 操作,稱為 multi, 将多個基本操作批次聚集為一個操作單元,要麼整體成功,要麼整體失敗。某些基本操作成功而某些失敗的情景

    永遠不會發生。

    Multiupdate 對于在 ZooKeeper 中建構維護一些全局一緻的資料結構非常有用。例如建構一個無向圖。

    ■ APIs

    -----------------------------------------------------------------------------------------------------------------------------------------    

    有兩個核心語言綁定 ZooKeeper 用戶端,一個是 Java, 一個是 C; 也有一個 contrib 建構用于 Perl, Python, 和 REST 用戶端。對于每種建構,都有執行

    同步操作或異步操作的選擇。下面是 ZooKeeper 類的 exists 操作的簽名,或者傳回一個 Stat 對象封裝 znode 的中繼資料,或者 null 如果 znode 不存在:

        public Stat exists(String path, Watcher watcher) throws KeeperException, InterruptedException

    異步方法:

        public void exists(String path, Watcher watcher, StatCallback cb, Object ctx)

    在 Java API 中,所有的異步方法都有 void 傳回類型,因為操作的結果是通過一個回調(callback)傳遞。調用者傳遞一個回調實作,當從 ZooKeeper 接收到

    響應時,它的方法會被調用。本例中,回調是 StatCallback 接口,具有如下方法:

        public void processResult(int rc, String path, Object ctx, Stat stat);

    rc 參數是傳回代碼,對應到 KeeperException 中定義的代碼。非零值表示一個異常,這種情況下 stat 參數傳回 null. path 和 ctx 參數對應 用戶端調用

    exists() 方法傳遞進來的參數,并可以用于識别這個回調是響應哪個請求。 ctx 參數可以是任意對象,可被用戶端用于當 path 對象不足以消除請求歧義時

    來識别請求。如果不需要,可以設定為 null .

    有兩個 C shared 庫。單線程的庫,zookeeper_st, 隻支援異步 API, 是針對 pthread 不可用準備的。大多數開發者會使用多線程庫(multithreaded library)

    zookeeper_mt, 它支援同步的和異步的 API. 對于如何建構和使用 C API, 參考 ZooKeeper 分發包 src/c 目錄的 README 檔案。

    ■ Watch 觸發器 (Watch triggers)

    -----------------------------------------------------------------------------------------------------------------------------------------    

    讀取操作 exists, getChildren, and getData 可以設定 watch, 并且 watch 由寫操作觸發: create, delete, and setData. ACL 操作不參與到觀察中。

    當一個 watch 觸發時,一個 watch 事件生成,該 watch event 的類型取決于 watch 和觸發它的操作:

        ● exists 操作上設定的 watch 會在被觀察的 znode 建立,删除,或其資料更新時觸發。

        ● getData 操作上設定的 watch 會在被觀察的 znode 被删除或有資料更新時觸發。建立時不放生觸發是因為該 znode 必須存在 getData 操作才能成功。

        ● getChildren 操作上設定的 watch 會在被觀察的 znode 的子節點被建立或删除時,或該 znode 被删除時觸發。可以通過檢視 watch 的事件類型得知

        是該 znode 還是其子節點被删除: NodeDeleted 表明是該 znode 被删除,而 NodeChildrenChanged 指明是它的一個子節點被删除。

                Watch creation operations and their corresponding triggers

            +===================+===============+=======================+===============+=======================+===================+

            | Watch creation    | create znode    | create child            | delete znode    |                        | setData            |

            +-------------------+---------------+-----------------------+---------------+-----------------------+-------------------+

            | exists            | NodeCreated    |                        | NodeDeleted    |                        | NodeDataChanged    |

            +-------------------+---------------+-----------------------+---------------+-----------------------+-------------------+

            | getData            |                |                        | NodeDeleted    |                        | NodeDataChanged    |

            +-------------------+---------------+-----------------------+---------------+-----------------------+-------------------+

            | getChildren        |                | NodeChildrenChanged    | NodeDeleted    | NodeChildrenChanged    |                    |

            +-------------------+---------------+-----------------------+---------------+-----------------------+-------------------+

        一個 watch event 包括與該 event 相關的 znode 的 path, 是以對于 NodeCreated 和 NodeDeleted 事件,可以簡單地通過觀察 path 分辨出哪個節點

        被建立或被删除。在 NodeChildrenChanged 事件之後确定是哪個子節點放生了變化,需要再次調用 getChildren 來擷取新的子節點清單。類似地,對于

        為了發現 NodeDataChanged 事件的新資料,需要調用 getData. 在這兩種情況下, znode 在接收 watch event 和執行讀取操作時, znode 可能發生變化,

        是以在編寫應用時應牢記這一點。

    ■ ACLs

    -----------------------------------------------------------------------------------------------------------------------------------------        

    每個 znode 被建立時都會帶有一個 ACL 清單,用于決定誰可以對它執行何種操作。

    ACL 依賴于驗證機制,用戶端向 ZooKeeper 标明其自身的過程。 ZooKeeper 提供了幾種驗證方式(authentication scheme):

        ● digest    : 用戶端通過使用者名和密碼驗證

        ● sasl        : 用戶端通過 Kerberos 驗證

        ● ip        : 用戶端通過 IP 位址驗證

    用戶端可以在建立一個 ZooKeeper 會話之後對自己進行身份驗證。雖然 znode 的 ACL 清單要求用戶端是通過身份驗證的,但 ZooKeeper 的身份驗證過程是

    可選的。用戶端必須自己進行身份驗證來支援對 znode 的通路。這裡有一個使用使用者名和密碼的 digest 方式進行身份驗證的例子:

        zk.addAuthInfo("digest", "tom:secret".getBytes());

    一個 ACL 是身份驗證方式(authentication scheme)、符合該方式的一個身份和一組權限的組合。例如,如果想給一個 IP 位址為 10.0.0.1 的用戶端,讀通路

    一個 znode, 應該在該 znode 上設定 ip 驗證方式的 ACL, ID 為 10.0.0.1, READ 許可權限。在 Java 中,應該如下建立一個 ACL 對象:

        new ACL(Perms.READ, new Id("ip", "10.0.0.1"));

    下表列出完整的權限。注意, exist 操作并不受 ACL 權限的控制,是以任何用戶端可以調用 exists 來檢索一個 znode 的 Stat 對象并查詢一個 znode 事實

    上是否存在。

            ACL permissions

        +===================+===========================================

        | ACL permission    | Permitted operations                        

        +-------------------+-------------------------------------------

        | CREATE            | create (a child znode)                    

        +-------------------+-------------------------------------------

        | READ                | getChildren

        |                    +-------------------------------------------

        |                    | getData                                    

        +-------------------+-------------------------------------------

        | WRITE                | setData                                    

        +-------------------+-------------------------------------------

        | DELETE            | delete (a child znode)                    

        +-------------------+-------------------------------------------

        | ADMIN                | setACL                                    

        +-------------------+-------------------------------------------

    在 ZooDefs.Ids class 中有一些預定義的 ACL, 包括 OPEN_ACL_UNSAFE, 它将所有的權限(除了 ADMIN 權限)授予所有人。

    此外, ZooKeeper 有一個插入式的身份驗證機制(a pluggable authentication mechanism), 如果需要的話,可以內建第三方身份驗證系統。

3.3 實作 (Implementation)

-----------------------------------------------------------------------------------------------------------------------------------------    

ZooKeeper 服務可運作于兩種不同的模式。獨立模式(standalone mode)下,隻有一個 ZooKeeper 伺服器,因其簡單性,比較适合于測試環境(甚至可嵌入到

單元測試, embedded in unit tests), 但不能確定高可用性和可恢複性。在生産環境中,ZooKeeper 在一個叢集的機器上運作于複制模式(replicated mode)

這個計算機叢集稱為集合體(an ensemble)。ZooKeeper 通過複制來實作高可用性,并且隻要集合體内多數機器可用就可以提供服務。例如,在一個有 5 個節

點的集合體中,任何兩個節點故障服務仍然可以工作,因為還保有三個的多數節點可用。注意,有 6 個節點的集合體也隻能容忍 2 台機器出現故障,因為如

果 3 台機器出現故障,剩下的 3 台機器沒有超過集合體的半數。出于這個原因,一個集合體通常包含奇數台機器。

從概念上來說,ZooKeeper 是非常簡單的:它所做的就是確定對 znode 樹所做每一個修改都會被複制到集合體中超過半數的機器上。如果少數機器發生故障,

那麼最少會有一部機器會保持最新狀态,其他剩下的複本最終也會更新到這個狀态。

然而,這個簡單想法的實作卻不簡單。ZooKeeper 使用一個稱為 Zab 的協定,這個協定分為兩個階段運作,可以無限重複:

    ■ 第一階段:上司者選舉 (Phase 1: Leader election)

    -------------------------------------------------------------------------------------------------------------------------------------

    集合體中的所有機器通過一個程序選擇出一個卓越的成員(electing a distinguished member),稱為上司者(leader). 其他機器稱為跟班(followers).

    一旦半數以上的(或法定數量的) followers 将它們的狀态與 leader 同步了,則這個階段結束。

    ■ 第而階段:原子廣播 (Phase 2: Atomic broadcast)

    -------------------------------------------------------------------------------------------------------------------------------------

    所有的寫入請求都會被轉發給 leader, 再由 leader 将更新廣播給 followers. 當半數以上的跟班已持久化這個變化,上司者送出更新,然後用戶端才

    收到響應說明更新已成功。這個完成一緻性的協定設計為原子性的,是以一個改變(change)要麼成功要麼失敗。

如果上司者出現故障,其餘的機器會選出另外一個 leader, 使用這個新的 leader 繼續像之前一樣提供服務。如果之前的 leader 之後恢複運作,它作為一個

follower 啟動。leader 選舉是非常快的,根據一個已公布的結果,大約 200 毫秒,是以選舉過程不會感覺到性能的降低。

集合體内所有機器在更新它們記憶體中的 znode 樹之前将更新寫入到磁盤。讀請求可以由任何機器提供服務,并且因為隻涉及記憶體查詢,是以非常塊。    

3.4 一緻性 (Consistency)

-----------------------------------------------------------------------------------------------------------------------------------------    

了解 ZooKeeper 的實作基本原理有助于了解其服務所提供的一緻性保證。對集合體中機器的術語 "leader" 和 "follower" 是恰當的,因為它表明了一個

follower 可能滞後于 leader 幾個更新。這就是實際上的一緻性,在一個修改被送出之前,隻要半數以上的成員而非集合體内全部成員都持久化了變更即可。

對于 ZooKeeper來說,理想的情況是将用戶端連結到與上司者狀态一緻的伺服器上。用戶端實際上可以被連接配接到 leader 上,但用戶端無法控制,甚至它自己都

不知道是否連接配接到上司者。

對 znode 樹的每個更新都會給定一個全局唯一辨別符,稱為 zxid(代表 ZooKeeper transaction ID). 更新是有次序的(ordered), 是以如果一個 zxid z1 比

z2 小,那麼,根據 ZooKeeper (分布式系統中唯一權威次序, the single authority on ordering in the distributed system), z1 一定在 z2 之前發生。

ZooKeeper 的設計中,以下幾點保證了資料的一緻性:

    ■ 順序一緻性 (Sequential consistency)

    -------------------------------------------------------------------------------------------------------------------------------------

    來自任意特定用戶端的更新都會按其發送順序被應用。也就是說,如果一個用戶端将 znode z 的值更新為 a, 在之後的操作中,它又将 z 的值更新為 b,

    則沒有用戶端能夠在看到 z 的值為 b 之後再看到值 a (如果沒有其他對 z 的更新).

    ■ 原子性 (Atomicity)

    -------------------------------------------------------------------------------------------------------------------------------------

    更新要麼成功要麼失敗。這意味着如果一個更新失敗,沒有用戶端會看到這個更新。

    ■ 單一系統映像 (Single system image)

    -------------------------------------------------------------------------------------------------------------------------------------

    一個用戶端會看到系統的同樣的視圖,不管它連接配接到哪個伺服器。這意味着,如果一個用戶端在同一個會話中連接配接到一台新的伺服器,它所看到的系統

    狀态不會比在之前伺服器上看到的更陳舊。當一個伺服器失效了,用戶端嘗試連接配接到集合體中的另一個伺服器時,所有狀态滞後于故障伺服器的伺服器都

    不會接受該連接配接請求,直到它這些伺服器将其狀态更新至故障伺服器的水準。

    ■ 耐久性 (Durability)

    -------------------------------------------------------------------------------------------------------------------------------------

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

    ■ 及時性 (Timeliness)

    -------------------------------------------------------------------------------------------------------------------------------------

    任何用戶端的系統視圖滞後都是有邊界的(bounded),過期不會超過幾十秒。這意味着與其允許一個用戶端看到非常陳舊的資料,還不如将伺服器關閉,

    強迫用戶端連接配接到一個狀态更新的伺服器。

出于性能的原因,讀取操作從 ZooKeeper 伺服器的記憶體中執行就可以滿足,并且不涉及在全局次序中的寫操作。這個屬性可能導緻如果用戶端通過 ZooKeeper

以外的機制進行通信,用戶端會看到不一緻的 ZooKeeper 狀态表現。

例如,用戶端 A 将 znode z 的值從 a 更新到 a' , A 告知 B 讀取 z, 并且 B 讀取到的 z 的值是 a 而不是 a' . 這與 ZooKeeper 的一緻性保證是完全相容

的(這種情況稱之為“跨用戶端視圖的同時一緻性”, the condition that it does not promise is called “simultaneously consistent cross-client views”)

為了阻止這種情況的發生, B 應該在讀取 z 的值之前,在 z 上調用 sync 操作。 sync 操作會強制 B 所連接配接的 ZooKeeper 伺服器趕上 leader 伺服器,這樣,

當 B 讀取 z 的值時,讀取到的将會是 A 所更新的(或後來更新的)。

    NOTE

    -------------------------------------------------------------------------------------------------------------------------------------

    有些令人困惑的是,sync 操作隻有作為異步調用(asynchronous call)可用。不需要等待其傳回,因為 ZooKeeper 確定這個伺服器的任何後續的操作都

    會在 sync 完成之後發生,即便是在 sync 完成之前發出的操作。

3.5 會話 (Sessions)

-----------------------------------------------------------------------------------------------------------------------------------------

每個 ZooKeeper 用戶端都配置為 ensemble 中的伺服器清單。啟動時,它嘗試連接配接伺服器清單中的一個伺服器。如果連接配接失敗,會嘗試清單中的另一伺服器,

等等,直到它或者成功連接配接到其中的一個或所有的 ZooKeeper 伺服器不可用而失敗。

一旦建立了與一個 ZooKeeper server 的連接配接,server 會為用戶端建立一個新的會話(session). 會話有一個逾時期限,這個逾時期限由建立會話的應用設定。

如果伺服器在逾時期限内沒有收到任何請求,則相應的會話會過期。一旦一個會話過期,就無法重新被打開它,并且任何與該會話相關聯的短暫 znode 都會丢

失。雖然會話過期是一個相對罕見的事件,因為會話都是長期存在的,但對于應用程式來說處理會話過期是非常重要的。

會話空閑超過一定的時間,用戶端會通過發送 ping 請求(也稱為心跳,also known as heartbeats)來保持會話存在。ping 包是有 ZooKeeper client 庫自動

發送的,是以代碼中不需要考慮如何維護會話。這個時間長度的選擇要足夠低,以便可以檢測出伺服器故障(由讀逾時展現),并且能夠在會話逾時期間能夠重新

連接配接到另外一個伺服器。

故障切換到另一個 ZooKeeper server 是由 ZooKeeper client 自動處理的,并且,至關重要的是,會話(及其關聯的短暫 znode)在從一個故障的伺服器切換

到另一個伺服器之後仍然有效。

在故障切換過程中,應用程式将接收到斷開連接配接和連接配接至伺服器的通知。 watch 通知在用戶端斷開連接配接時不會被傳遞,但當用戶端成功恢複連接配接時,watch

通知會被傳輸。同樣,在用戶端重新連接配接到另一個伺服器過程中,如果應用程式試圖執行一個操作,這個操作将會失敗。這充分說明在真實的 ZooKeeper 應用

中,處理連接配接丢失異常(connection loss exceptions)的重要性。

    ■ 時間 (Time)

    -------------------------------------------------------------------------------------------------------------------------------------    

    ZooKeeper 中有幾個時間參數。tick time 是 ZooKeeper 中基本的時間周期,并被集合體内的伺服器用來定義互相互動的時間排程。其他的設定都是根據

    tick time 來定義的,或者至少受它的限制。例如,會話逾時(The session timeout), 不能低于 2 tick 或大于 20 tick. 如果嘗試設定會話逾時在這個

    範圍之外,它會被修改為這個範圍之内。

    通常将 tick time 參數設定為 2 秒(2000 毫秒), 對應于允許的會話逾時範圍是 4 到 40 秒。

    在選擇會話逾時設定時有幾點需要考慮。低的會話逾時導緻更快的機器故障檢測。在組成員關系的例子中,會話逾時就是用于将故障主機從組中删除的時間。

    但要避免将會話逾時時間設得太低,因為繁忙的網絡會導緻資料包傳輸延遲,進而可能會無意中導緻會話過期。這種情況下,機器可能會出現振蕩現象(

    flap):在很短的資料間隔内反複地離開然後又重新加入到組中。

    對于那些建立較複雜暫時狀态的應用程式來說,更适合設定較長的會話逾時,因其重建的代價較高。有些情況下,可以設計應用程式在會話逾時周期内重新

    啟動,進而避免出現會話過期。這适合于執行維護和更新。伺服器會為每個會話配置設定一個唯一的 ID 和 password,如果在建立連接配接時把它們傳遞給

    ZooKeeper,就可能恢複一個會話(隻要它沒有過期)。應用程式能是以安排優雅的關閉(a graceful shutdown), 通過在程序重新開機之前将會話 ID 和密碼

    存儲到穩定的存儲器中,重新開機之後從存儲器中擷取會話 ID 和 password, 然後恢複會話。

    可以将這個特性看做一種用于幫助避免會話過期的優化技術,但不能是以忽略會話過期的處理,如果一部機器意外發生故障這還是可能發生的,或者,

    即使應用程式正常關閉,也可能因任何原因導緻它沒有在會話過期之前完成重新開機。

    一般的規則是,在較大的 ZooKeeper 集合體中,會話逾時的設定應該越大。連接配接逾時、讀取逾時和 ping 周期都在内部被定義為集合體中伺服器數量

    的函數,是以,集合體中伺服器數量增多,這些參數的值越小。如果頻繁遇到連接配接丢失的情況,應考慮增大逾時值。可以使用 JMX 來監控 ZooKeeper 的

    度量名額,例如請求延時的統計資訊。

3.6  狀态 (States)

-----------------------------------------------------------------------------------------------------------------------------------------

ZooKeeper 對象在其生命周期内會經曆幾個不同的狀态,可以在任何時間通過調用 getState() method 查詢其狀态。

    public States getState()

States 是一個枚舉(enum), 表現為 ZooKeeper 對象可能處于的不同狀态。一個 ZooKeeper 執行個體在一個時刻隻能處于一種狀态。一個新建立的 ZooKeeper

執行個體在其嘗試與 ZooKeeper 服務建立連接配接時處于 CONNECTING 狀态。一旦連接配接建立,它會進入 CONNECTED 狀态。

                            CONNECTING        

                            CONNECTED

                            CLOSED

用戶端使用 ZooKeeper 對象通過注冊一個 Watcher 對象能夠接收狀态轉換通知。進入 CONNECTED 狀态,Watcher 會接收到一個 WatchedEvent, 它的

KeeperState 的值為 SyncConnected.

    NOTE

    -------------------------------------------------------------------------------------------------------------------------------------    

    一個 ZooKeeper Watcher 對象負有雙重責任:一方面它可用于接收 ZooKeeper 狀态變化通知(本節所描述的),另一方面可用于接收 znode 變化的通知

    (Watch triggers 中所描述的)。傳入 ZooKeeper 對象構造器的 watcher (預設的)用于狀态變化,但對于 znode 變化的 watcher, 可以使用一個指定的

    Watcher 執行個體(通過在向某個合适的讀操作傳遞一個 watcher 執行個體), 或者共享使用一個預設 watcher,通過讀操作中的布爾辨別設定。

ZooKeeper 執行個體可以斷開,然後重新連接配接到 ZooKeeper 服務,狀态在 CONNECTED 和 CONNECTING 之間轉換。如果斷開連接配接,watcher 會收到 Disconnected

事件。注意,這些狀态的變化都是由 ZooKeeper 執行個體自己發起的,如果連接配接丢失,它會自動嘗試重新連接配接。

如果調用了 close() method, 或者會話逾時,由 KeeperState 的值為 Expired 表明, ZooKeeper 執行個體可以轉換為第三種狀态,CLOSED。 一旦進入 CLOSED

狀态,ZooKeeper 對象就不再認為是活躍的(可以通過調用 States 上 isAlive() method 測試)并且不能再被使用。要重新連接配接到 ZooKeeper 服務,用戶端必須

建構一個新的 ZooKeeper  執行個體。

Hadoop 之 ZooKeeper (二)

繼續閱讀