Zookeeper クラスター構築
この記事では、Debian 上に 5 ノードの Zookeeper クラスターを構築する一連の流れを記録します。まず Znode や ZAB プロトコルといった基礎概念を整理し、そのあと実際に設定を行い、Leader ダウン時の選出実験を一度やってみます。
デプロイ環境
オペレーティングシステム:Debian 11.5.0
実行環境:OpenJDK-1.8.0_332 64-Bit Server VM
Zookeeper バージョン:3.8.0
公式の紹介
Zookeeper 公式ドキュメントのアドレス(ダウンロードした zip アーカイブ内の \docs ディレクトリにもドキュメントが同梱されています):ZooKeeper: Because Coordinating Distributed Systems is a Zoo (apache.org)
ZooKeeper は、分散アプリケーション向けの分散型オープンソースコーディネーションサービスです。シンプルなプリミティブのセットを公開しており、分散アプリケーションはこれらのプリミティブをベースに、より高レベルの同期、設定の維持管理、グループ・ネーミングサービスを実装できます。プログラミングしやすいように設計されており、ファイルシステムのディレクトリツリー構造に似たデータモデルを使用します。Java で動作し、Java と C のバインディングを提供しています。
ZooKeeper の実装は、高パフォーマンス、高可用性、厳密に順序付けられたアクセスを非常に重視しています。高パフォーマンスであることは大規模分散システムで使えることを意味し、信頼性はそれが単一障害点にならないことを意味し、厳密な順序付けはクライアント側で複雑な同期プリミティブを実装できることを意味します。

公式の紹介から、Zookeeper は主に meta 情報の保存と設定情報の同期に用いられる、高パフォーマンス・高可用・単一障害点のないアプリケーションサービスである、と結論づけられます。
個人的な理解
日本語に訳すと「動物園の飼育員」です。Apache のプロジェクトはほとんどが動物をロゴや名前に使っているので、Zookeeper のロゴは飼育員のような姿で、シャベルまで持っています(doge)。Zookeeper 自体のアーキテクチャ設計もシンプルで、とっつきやすいものです。
Apache Zookeeper は Apache Hadoop のアーキテクチャ全体の一員でもあり、Hadoop アーキテクチャの中で統一的な設定管理、ネーミングサービス、重要データの保存などの機能を担うミドルウェアです。一言でまとめると:Zookeeper = ファイルシステム + 通知メカニズム、です。
Zookeeper(以下 ZK)自体のデータ保存方式は Key-Value 形式に似ていますが、ZK ではデータが Znode という形で保存され、Linux のファイルシステム管理に似ています。Key はすべてファイルパスの形で表現され、多階層のディレクトリを持ちます。これは Redis の Key ともよく似ていますが、Redis の Key が : 記号で階層を分ける習慣なのに対し、ZK は完全に Linux の / 記号で階層を分けます。ZK のデータはメモリ上に保存され、ハードディスクに永続化されます。メモリ内で Znode のツリー状データ構造(ファイルシステム)を維持しています。
データ構造
ZK の Value は豊富なデータ構造を提供していません(Redis が list、set、hashmap、zset、string など多様な構造を提供しているのとは違います)。ZK の Value は String 型のみです。ZK の位置付けはそもそもデータベースではなく、アーキテクチャ内の重要な設定情報を保存し、通知と同期の機能を提供するために設計されたものです。そのため各 Znode の最大保存データは 1M です。
Znode のタイプ
1) 永続ノード
永続ノードは作成後、人為的に Delete コマンドで削除しない限り、ずっと存在し続けます。
2) 一時ノード
一時ノードは接続クライアントのセッションに依存します。作成後、クライアントが接続を維持し続けていれば一時ノードは存在し続け、クライアントの接続が切断されると一時ノードは削除されます。この仕組みの使いどころが見えてきませんか?レジストリセンター(サービスレジストリ)の機能にぴったりです——Dubbo が起動すれば接続し、ダウンすれば Znode ノードが削除され、他のクライアントに通知されます。
3) シーケンシャルノード
シーケンシャルノードを作成すると、ノードパスの後ろに自動的に 10 桁の数字がカウンターとして追加されます。
例えば /node というシーケンシャルノードを作成すると、file path は /node0000000001 になります。
次にもう一度 /node ノードを作成すると、カウンターに +1 して新しい Znode ノードが作成されます(古い /node0000000001 は削除されず、そのまま残ります)。このときの file path は /node0000000002 です。
ただし、カウンターの値が 2147483647 を超えるとオーバーフローが発生します。
4) 一時シーケンシャルノード
一時ノードの特性 + シーケンシャルノードの特性です。
ZK の特性
- 順序一貫性 - クライアントからの更新は、その送信順序どおりに適用される。
- 原子性 - 更新は成功するか失敗するかのどちらか。部分的な結果は存在しない。
- 単一システムイメージ - (グローバルなデータ一貫性)クラスター内の各 Server は同一のデータレプリカを保持しており、Client がどの Server に接続してもデータは一貫している【理論上は一貫しているものの、データ同期の過程で一時的に不一致になる瞬間はある。ただし sync コマンドで解決できる】。
- 信頼性 - 更新が適用されると、クライアントが更新を上書きするまで、その時点からずっと存在し続ける。
- 適時性 - システムのクライアントビューが特定の時間範囲内で最新であることを保証する。
このうち原子性は ZAB プロトコルによって保証されています。ZAB プロトコルの正式名称は Zookeeper Atomic Broadcast(Zookeeper アトミックブロードキャスト)です。
ZAB プロトコル
ZAB には 2 つのモードがあります。1 つはメッセージブロードキャストモード、もう 1 つはクラッシュリカバリーモードです。
メッセージブロードキャストモード
Zookeeper クラスターにおけるデータレプリカの伝搬戦略には、メッセージブロードキャストモードが採用されています。Zookeeper のデータレプリカの同期方式は 2PC に似ていますが、同じではありません。2PC ではコーディネーターがすべての参加者から ACK 確認メッセージのフィードバックを受け取るのを待ってから commit メッセージを送信する必要があり、すべての参加者が全員成功するか全員失敗するかを要求するため、2PC は深刻なブロッキング問題を引き起こします。
一方 Zookeeper で Leader が Follower の ACK フィードバックを待つというのは、過半数の Follower から成功のフィードバックがあれば十分という意味で、すべての Follower からのフィードバックを受け取る必要はありません。これにより待ち時間を減らすことができます。
クラッシュリカバリーモード
Leader サーバーがクラッシュしたり、ネットワークの問題で Leader サーバーが過半数の Follower との連絡を失ったりすると、クラッシュリカバリーモードに入り、新しい Leader を選出し直します。
クラスター構築(5 ノード)
ZK のクラスターには最低 3 つのノードが必要で、ノード数は奇数でなければなりません。許容できるダウン台数はノード数 / 2 です。3 台なら 1 台のダウンを許容してサービスを継続でき、5 台なら 2 台のダウンを許容してサービスを継続できます。従うルールは、過半数のノードが正常であれば、クラスター全体は外部へのサービス提供を継続できるというものです。
ZK クラスターにおける 3 つのロール
Leader(リーダー):クラスター全体で Leader に選出されるノードは 1 つだけで、書き込み操作ができるのは Leader だけです。(この設計により ZK の書き込みパフォーマンスがボトルネックになる運命にありますが、ほとんどがメモリ操作であり、各 Znode ノードのデータ量も非常に小さいため、それでも高い書き込みパフォーマンスを維持できます。)
Follower(フォロワー):Follower はクラスター内に複数存在でき、Leader とハートビート接続を維持する必要があります。このノードは読み取りのみ可能で書き込みはできず、Leader ノードがダウンした際には新しい Leader の選出投票に参加する必要があります。
Observer(オブザーバー):Observer ノードはクラスター全体の中で最も末端の存在で、読み取りのみ可能で、Leader 選出に参加できません。なぜ Observer のようなロールが必要なのでしょうか?ZK クラスターの書き込み操作は過半数の Follower の同期確認があって初めて書き込み成功となるため、ZK クラスター内のノードが多いほど書き込みパフォーマンスは低下します。ZK クラスターの読み取り能力を高めつつ書き込みパフォーマンスに影響を与えないために、Observer のようなノードが設計されたのです。Observer は Leader のデータを同期し、読み取り操作を提供することだけを担当します。

公式が示している ZK クラスターのノード数とパフォーマンスのグラフを見ると、ノード数の増加に伴い、ZK クラスターが処理するリクエスト数も上昇しています。ただし個人的には、正確に言えば上昇するのは読み取り能力で、書き込み能力は低下するはずだと思います。ノードが多いほど、Leader がデータを書き込む際に ZAB プロトコルで他の Follower ノードに同期して確認を取る必要があり、過半数の Follower ノードがデータ同期に成功して初めて書き込み成功となるため、ノード数の増加は必然的に書き込み能力の低下を招きます。この設計は ZK が大量書き込みのシナリオに向いていないことも示しており、公式が示す読み書き比率は 7:3 か 8:2 が最適とされています。
zoo.cfg の設定(このチュートリアルではダウンロードと解凍は省略します)
zip アーカイブを解凍したら、conf フォルダに入り、zoo_sample.cfg を zoo.cfg にリネームするか、コピーして zoo.cfg という名前にします。
zoo.cfg 内のいくつかのデフォルト設定を変更します。最も重要なのは dataDir のフォルダパスの変更です。ZK はデフォルトで tmp パスに保存しますが、Linux システムは不定期にこの一時ディレクトリを空にするため、他のフォルダパスに変更する必要があります。

変更が終わったら保存を忘れずに。私が設定したパスは /opt/zookeeper/data で、以降の各ノードでも設定が必要です。
myid ファイルの作成
変更後の dataDir パスのフォルダ内に myid ファイルを作成し、ファイル内に現在のノードの一意な識別 ID を書き込みます。
以降の各ノードでも、それぞれのノードに対応する識別 ID を設定する必要があり、重複は許されません。
クラスターノードリストの設定
各ノードの zoo.cfg に server ノードのアドレスを追加します:

server.1 の 1 は myid の識別子を表し、その後ろの値にはノードの ip でも hostname でも書けます。2888 ポートはデータ同期に、3888 ポートは選出投票に使われます。
全ノードの起動
半数未満のノードしか起動していない場合、クラスターは一切のサービスを提供しません:
このとき例外がスローされ ZK に接続できないため、すべてのノードを起動する必要があります。
クラスターステータスの検証
すべてのノードが正常に起動した後:
zkServer.sh の status コマンドで、現在のノードのロールと起動ステータスを確認できます。
zkCli.sh でクラスターに接続し、コマンドを実行して正常かどうかテストします:

さらに他のノードでもコマンドテストを行います:

すべて正常です。これで最もシンプルな ZK クラスターの構築に成功しました。今回の構築は最も基本的な流れに基づいたものであり、今後は設定の変更を通じてクラスター全体のディテールをさらにチューニングし、ssl 通信とパスワードを追加してクラスター全体のセキュリティを強化することができます。
リスク実験
テストの強度をさらに上げて、Leader ノードを停止させ、クラスターが自動的に新しい Leader ノードを選出するかどうかを確認してみます。
Leader ノードを見つけて、そのノードのサービスを停止します。

現在の Leader ノードは debian4 サーバーなので、すぐに zkServer.sh stop コマンドを実行してその ZK サービスを停止します。

zkServer.sh status コマンドで確認すると、debian5 サーバーが新しい Leader ノードに選出されており、クラスターの選出が完全に正常であることを示しています。

公式の設定パラメータ:动物园管理员:因为协调分布式系统是一个动物园 (apache.org)
まとめ
今回の構築は最も基礎的な流れで進めました。dataDir を変更し、myid を書き、zoo.cfg にすべての server ノードを登録し、1 台ずつ起動する。クラスターが外部にサービスを提供できるかどうかのコアルールはただ 1 つ——過半数のノードが生存していること。リスク実験でもこれが検証できました。Leader を停止すると、残りのノードがすぐに新しい Leader を選出し、サービスは中断しませんでした。Znode モデルと ZAB プロトコルの過半数確認メカニズムを理解すれば、これらの設定項目の背後にある設計意図もすべて腑に落ちます。プロダクション環境で使う前には、ssl と認証の設定も忘れずに追加しましょう。
COMMENTS