Apache > ZooKeeper
 

ZooKeeper プログラマーズガイド

ZooKeeper を使用した分散アプリケーションの開発

はじめに

このドキュメントは、ZooKeeper の調整サービスを利用した分散アプリケーションを作成したい開発者向けのガイドです。概念的な情報と実践的な情報が含まれています。

このガイドの最初の 4 つのセクションでは、さまざまな ZooKeeper の概念について高レベルで説明します。これらは、ZooKeeper がどのように機能するか、およびそれを使用する方法を理解するために必要です。ソースコードは含まれていませんが、分散コンピューティングに関連する問題に精通していることを前提としています。この最初のグループのセクションは次のとおりです。

次の 4 つのセクションでは、実践的なプログラミング情報を提供します。これらは次のとおりです。

この本は、その他の役立つ ZooKeeper 関連情報へのリンクを含む付録で締めくくられます。

このドキュメントの情報のほとんどは、スタンドアロンの参考資料としてアクセスできるように記述されています。ただし、最初の ZooKeeper アプリケーションを開始する前に、少なくともZooKeeper データモデルZooKeeper 基本操作の章を読むことをお勧めします。

ZooKeeper データモデル

ZooKeeper には、分散ファイルシステムによく似た階層的な名前空間があります。唯一の違いは、名前空間内の各ノードに子だけでなくデータも関連付けることができることです。ファイルがディレクトリにもなれるファイルシステムのようなものです。ノードへのパスは常に、正規化された絶対スラッシュ区切りのパスとして表現されます。相対参照はありません。次の制約に従って、パスには任意のユニコード文字を使用できます。

ZNode

ZooKeeper ツリー内のすべてのノードはznodeと呼ばれます。 Znode は、データ変更、acl 変更のバージョン番号を含む stat 構造を維持します。stat 構造にはタイムスタンプもあります。バージョン番号は、タイムスタンプとともに、ZooKeeper がキャッシュを検証し、更新を調整できるようにします。znode のデータが変更されるたびに、バージョン番号が増加します。たとえば、クライアントがデータを取得すると、データのバージョンも受信します。また、クライアントが更新または削除を実行する場合、変更する znode のデータのバージョンを提供する必要があります。提供されたバージョンがデータの実際のバージョンと一致しない場合、更新は失敗します(この動作はオーバーライドできます)。

注記

分散アプリケーションエンジニアリングでは、ノードという言葉は、汎用ホストマシン、サーバー、アンサンブルのメンバー、クライアントプロセスなどを指す場合があります。 ZooKeeper ドキュメントでは、znodeはデータノードを指します。サーバーは、ZooKeeper サービスを構成するマシンを指します。クォーラムピアは、アンサンブルを構成するサーバーを指します。クライアントは、ZooKeeper サービスを使用するホストまたはプロセスを指します。

Znode は、プログラマーがアクセスする主なエンティティです。 ここでは言及する価値のあるいくつかの特性があります。

ウォッチ

クライアントは znode にウォッチを設定できます。 その znode への変更はウォッチをトリガーし、ウォッチをクリアします。 ウォッチがトリガーされると、ZooKeeper はクライアントに通知を送信します。 ウォッチの詳細については、ZooKeeper ウォッチのセクションを参照してください。

データアクセス

名前空間内の各 znode に保存されたデータは、アトミックに読み書きされます。 読み取りは znode に関連付けられたすべてのデータバイトを取得し、書き込みはすべてのデータを置き換えます。 各ノードには、誰が何を実行できるかを制限するアクセス制御リスト(ACL)があります。

ZooKeeper は、一般的なデータベースや大きなオブジェクトストアとして設計されたものではありません。代わりに、調整データを管理します。このデータは、構成、ステータス情報、ランデブーなどの形式で提供できます。さまざまな形式の調整データに共通するプロパティは、それらが比較的小さいことです。キロバイト単位で測定されます。 ZooKeeper クライアントとサーバーの実装には、znode が 1M 未満のデータであることを保証するための健全性チェックがありますが、データは平均してそれよりもはるかに少なくする必要があります。比較的大量のデータを操作すると、一部の操作に他の操作よりも時間がかかるようになり、より多くのデータをネットワーク経由でストレージメディアに移動する必要があるため、一部の操作のレイテンシーに影響します。大量のデータストレージが必要な場合は、そのようなデータを処理する一般的なパターンは、NFS や HDFS などのバルクストレージシステムにデータを保存し、ZooKeeper にストレージロケーションへのポインターを保存することです。

エフェメラルノード

ZooKeeper には、エフェメラルノードの概念もあります。これらの znode は、znode を作成したセッションがアクティブである限り存在します。セッションが終了すると、znode は削除されます。この動作のため、エフェメラルノードは子を持つことが許可されていません。セッションのエフェメラルのリストは、getEphemerals() api を使用して取得できます。

getEphemerals()

指定されたパスのセッションによって作成されたエフェメラルノードのリストを取得します。パスが空の場合、セッションのすべてのエフェメラルノードを一覧表示します。ユースケース - サンプルのユースケースとしては、重複データエントリのチェックのためにセッションのエフェメラルノードのリストを収集する必要があり、ノードが順番に作成されているため、重複チェックの名前がわからない場合があります。その場合、getEphemerals() api を使用して、セッションのノードのリストを取得できます。これは、サービス検出の典型的なユースケースになる可能性があります。

シーケンスノード - 一意な名前付け

znode を作成するときに、ZooKeeper にパスの最後に単調増加するカウンターを追加するように要求することもできます。このカウンターは親 znode に固有のものです。カウンターの形式は %010d です。つまり、0 (ゼロ) パディング付きの 10 桁 (カウンターはソートを簡単にするためにこの形式でフォーマットされます)、つまり"0000000001" です。この機能の使用例については、キューのレシピを参照してください。 注:次のシーケンス番号を格納するために使用されるカウンターは、親ノードによって維持される符号付き int(4バイト)です。カウンターは、2147483647を超えてインクリメントされるとオーバーフローします(名前「"-2147483648").

コンテナノード

3.5.3 で追加

ZooKeeper には、コンテナ znode の概念があります。コンテナ znode は、リーダー、ロックなどのレシピに役立つ特殊な目的の znode です。コンテナの最後の子供が削除されると、コンテナは将来のある時点でサーバーによって削除される候補になります。

このプロパティを考えると、コンテナ znode 内に子を作成するときに KeeperException.NoNodeException を取得する準備をしておく必要があります。つまり、コンテナ znode 内に子 znode を作成するときは、常に KeeperException.NoNodeException をチェックし、発生した場合はコンテナ znode を再作成してください。

TTLノード

3.5.3 で追加

PERSISTENT または PERSISTENT_SEQUENTIAL znode を作成するときに、オプションで znode の TTL をミリ秒単位で設定できます。znode が TTL 内で変更されず、子がない場合、将来のある時点でサーバーによって削除される候補になります。

注:TTL ノードは、デフォルトで無効になっているため、システムプロパティを介して有効にする必要があります。詳細については、管理者ガイドを参照してください。適切なシステムプロパティを設定せずに TTL ノードを作成しようとすると、サーバーは KeeperException.UnimplementedException をスローします。

ZooKeeper における時間

ZooKeeper は複数の方法で時間を追跡します

ZooKeeper Stat 構造

ZooKeeper の各 znode の Stat 構造は、以下のフィールドで構成されています。

ZooKeeper セッション

ZooKeeper クライアントは、言語バインディングを使用してサービスへのハンドルを作成することにより、ZooKeeper サービスとのセッションを確立します。作成されると、ハンドルは最初に CONNECTING 状態になり、クライアントライブラリは ZooKeeper サービスを構成するサーバーのいずれかに接続しようとします。この時点で CONNECTED 状態に切り替わります。通常の動作中、クライアントハンドルはこれらの2つの状態のいずれかになります。セッションの有効期限切れや認証の失敗など、回復不能なエラーが発生した場合、またはアプリケーションが明示的にハンドルを閉じた場合、ハンドルは CLOSED 状態に移行します。次の図は、ZooKeeper クライアントの可能な状態遷移を示しています。

State transitions

クライアントセッションを作成するには、アプリケーションコードは、ZooKeeperサーバーに対応する host:port ペアのコンマ区切りリストを含む接続文字列を提供する必要があります(例: "127.0.0.1:4545" または "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002")。ZooKeeperクライアントライブラリは、任意のサーバーを選択して接続を試みます。この接続が失敗した場合、または何らかの理由でクライアントがサーバーから切断された場合、クライアントは接続が(再)確立されるまで、リスト内の次のサーバーを自動的に試行します。

3.2.0 で追加:オプションの「chroot」サフィックスを接続文字列に追加することもできます。これにより、(unix の chroot コマンドと同様に)このルートからの相対パスとしてすべてのパスを解釈しながらクライアントコマンドが実行されます。使用した場合、例は "127.0.0.1:4545/app/a" または "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" のようになります。クライアントは "/app/a" をルートとして、すべてのパスはこのルートからの相対パスとなります。つまり、 "/foo/bar" を取得/設定/などすると、"/app/a/foo/bar" (サーバーの視点から)に対して操作が実行されます。この機能は、特定の ZooKeeper サービスの各ユーザーを異なるルートにできるマルチテナント環境で特に役立ちます。これにより、各ユーザーはアプリケーションを "/" をルートとしたようにコーディングできるため、再利用がはるかに簡単になりますが、実際の場所(たとえば /app/a)はデプロイ時に決定できます。

クライアントが ZooKeeper サービスへのハンドルを取得すると、ZooKeeper はクライアントに割り当てる 64 ビット数として表される ZooKeeper セッションを作成します。クライアントが別の ZooKeeper サーバーに接続する場合、接続ハンドシェイクの一部としてセッション ID を送信します。セキュリティ対策として、サーバーはどの ZooKeeper サーバーでも検証できるセッション ID のパスワードを作成します。パスワードは、クライアントがセッションを確立するときにセッション ID とともにクライアントに送信されます。クライアントは、新しいサーバーでセッションを再確立するたびに、このパスワードをセッション ID とともに送信します。

ZooKeeper セッションを作成するための ZooKeeper クライアントライブラリ呼び出しへのパラメータの1つは、ミリ秒単位のセッションタイムアウトです。クライアントは要求されたタイムアウトを送信し、サーバーはクライアントに与えることができるタイムアウトで応答します。現在の実装では、タイムアウトは(サーバー構成で設定されているように)tickTime の最小2倍、最大20倍である必要があります。ZooKeeper クライアントAPIは、ネゴシエートされたタイムアウトへのアクセスを許可します。

クライアント(セッション)がZKサービスクラスターから分割されると、セッションの作成中に指定されたサーバーのリストの検索を開始します。最終的に、クライアントと少なくとも1つのサーバー間の接続が再確立されると、セッションは(セッションタイムアウト値内で再接続した場合)再度「接続済み」状態に移行するか、(セッションタイムアウト後に再接続した場合)「期限切れ」状態に移行します。切断のために新しいセッションオブジェクト(cバインディングの新しいZooKeeper.classまたはzookeeperハンドル)を作成することはお勧めしません。ZKクライアントライブラリは、再接続を処理します。特に、クライアントライブラリには「群集効果」などを処理するためのヒューリスティックが組み込まれています。セッションの有効期限切れが通知された場合にのみ、新しいセッションを作成してください(必須)。

セッションの有効期限切れは、クライアントではなく、ZooKeeperクラスター自体によって管理されます。ZKクライアントがクラスターとのセッションを確立すると、上記で詳述した「タイムアウト」値を提供します。この値は、クライアントのセッションの有効期限がいつ切れるかを判断するためにクラスターによって使用されます。有効期限切れは、クラスターが指定されたセッションタイムアウト期間内にクライアントからの応答(つまり、ハートビートなし)を受信しなかった場合に発生します。セッションの有効期限切れ時には、クラスターはそのセッションが所有するすべてのエフェメラルノードを削除し、接続されているすべてのクライアントに即座に変更を通知します(それらのznodeを監視している人)。この時点で、期限切れセッションのクライアントはまだクラスターから切断されており、クラスターへの接続を再確立できるまでセッションの有効期限切れは通知されません。クライアントは、クラスターとのTCP接続が再確立されるまで切断状態のままであり、その時点で、期限切れセッションのウォッチャーは「セッションの有効期限切れ」通知を受信します。

期限切れセッションのウォッチャーによって認識される、期限切れセッションの状態遷移の例

  1. 「接続済み」:セッションが確立され、クライアントはクラスターと通信しています(クライアント/サーバー間の通信は正常に動作しています)。
  2. ...クライアントはクラスターから分割されます
  3. 「切断済み」:クライアントはクラスターとの接続を失いました
  4. ...時間が経過し、「タイムアウト」期間の後、クラスターはセッションを期限切れにします。クライアントはクラスターから切断されているため、何も表示されません
  5. ...時間が経過し、クライアントはクラスターとのネットワークレベルの接続を取り戻します
  6. 「期限切れ」:最終的にクライアントはクラスターに再接続し、その後、期限切れが通知されます

ZooKeeper セッション確立呼び出しへの別のパラメータは、デフォルトのウォッチャーです。ウォッチャーは、クライアントで状態の変化が発生すると通知されます。たとえば、クライアントがサーバーへの接続を失った場合、クライアントは通知されます。また、クライアントのセッションの有効期限が切れた場合などにも通知されます。このウォッチャーは、初期状態が切断されていると見なす必要があります(つまり、クライアントライブラリからウォッチャーに状態変更イベントが送信される前)。新しい接続の場合、ウォッチャーに送信される最初のイベントは通常、セッション接続イベントです。

セッションは、クライアントによって送信されたリクエストによって維持されます。セッションがタイムアウトする時間だけアイドル状態になっている場合、クライアントはセッションを維持するためにPINGリクエストを送信します。このPINGリクエストにより、ZooKeeperサーバーはクライアントがまだアクティブであることを知ることができるだけでなく、クライアントはZooKeeperサーバーへの接続がまだアクティブであることを確認することもできます。PINGのタイミングは、デッド接続を検出し、新しいサーバーに再接続するための妥当な時間を確保するのに十分なほど保守的です。

サーバーへの接続が正常に確立される(接続される)と、クライアントライブラリがconnectionloss(cバインディングの結果コード、Javaの例外--バインディング固有の詳細についてはAPIドキュメントを参照してください)を生成する基本的には2つのケースがあります。同期操作または非同期操作が実行され、次のいずれかが保持されている場合です。

  1. アプリケーションは、もはやアクティブ/有効ではないセッションで操作を呼び出します。
  2. ZooKeeperクライアントは、保留中の操作がサーバーにある場合にサーバーから切断されます。つまり、保留中の非同期呼び出しがあります。

3.2.0 で追加 -- SessionMovedException。SessionMovedExceptionと呼ばれる、通常はクライアントには表示されない内部例外があります。この例外は、異なるサーバーで再確立されたセッションの接続でリクエストが受信されたために発生します。このエラーの通常の原因は、クライアントがサーバーにリクエストを送信するものの、ネットワークパケットが遅延するため、クライアントがタイムアウトして新しいサーバーに接続することです。遅延したパケットが最初のサーバーに到着すると、古いサーバーはセッションが移動したことを検出し、クライアント接続を閉じます。クライアントは通常、これらの古い接続からは読み取らないため、このエラーは表示されません。(古い接続は通常、閉じられます。)この状態が表示される可能性がある1つの状況は、2つのクライアントが保存されたセッションIDとパスワードを使用して同じ接続を再確立しようとする場合です。クライアントの1つが接続を再確立し、2番目のクライアントが切断されます(これにより、ペアは接続/セッションを無期限に再確立しようとします)。

サーバーリストの更新。クライアントが、ZooKeeperサーバーに対応するhost:portペアの新しいコンマ区切りリストを提供することにより、接続文字列を更新できるようにします。この関数は、新しいリスト内のサーバーごとの接続数が均一になるように、確率的な負荷分散アルゴリズムを呼び出し、クライアントが現在のホストから切断される可能性があります。クライアントが接続されている現在のホストが新しいリストに含まれていない場合、この呼び出しは常に接続が切断される原因になります。それ以外の場合、決定は、サーバーの数が増加したか減少したか、およびその量に基づいて行われます。

たとえば、以前の接続文字列に3つのホストが含まれていて、リストにこれらの3つのホストとさらに2つのホストが含まれている場合、3つのホストのそれぞれに接続されたクライアントの40%が負荷を分散するために新しいホストの1つに移動します。アルゴリズムにより、クライアントは確率0.4で接続先の現在のホストへの接続をドロップし、この場合、ランダムに選択された2つの新しいホストのいずれかにクライアントが接続するようになります。

別の例--5つのホストがあり、リストを更新して2つのホストを削除すると仮定します。残りの3つのホストに接続されているクライアントは接続されたままになりますが、削除された2つのホストに接続されているすべてのクライアントは、ランダムに選択された3つのホストのいずれかに移動する必要があります。接続が切断された場合、クライアントは、ラウンドロビンではなく、確率的なアルゴリズムを使用して接続する新しいサーバーを選択する特別なモードに移行します。

最初の例では、各クライアントは0.4の確率で切断を決定しますが、一度決定が下されると、新しいランダムなサーバーに接続を試みます。新しいサーバーのいずれにも接続できない場合にのみ、古いサーバーへの接続を試みます。サーバーを見つけるか、新しいリスト内のすべてのサーバーを試して接続に失敗した後、クライアントは通常の動作モードに戻り、connectStringから任意のサーバーを選択して接続を試みます。それが失敗した場合は、ラウンドロビンで異なるランダムサーバーを試行し続けます(最初にサーバーを選択するために使用されるアルゴリズムを上記参照)。

ローカルセッション。3.5.0で追加。主にZOOKEEPER-1147によって実装されています。

localSessionsUpgradingEnabledが無効になっている場合

localSessionsUpgradingEnabledが有効になっている場合

ZooKeeper ウォッチ

ZooKeeperでのすべての読み取り操作 - getData()getChildren()、およびexists() - には、副作用として監視を設定するオプションがあります。以下は、ZooKeeperの監視の定義です。監視イベントとは、監視を設定したクライアントに送信される1回限りのトリガーであり、監視が設定されたデータの変更が発生すると発生します。この監視の定義では、考慮すべき3つの重要なポイントがあります。

監視は、クライアントが接続されているZooKeeperサーバーでローカルに維持されます。これにより、監視を軽量に設定、維持、およびディスパッチできます。クライアントが新しいサーバーに接続すると、セッションイベントの監視がトリガーされます。サーバーから切断されている間、監視は受信されません。クライアントが再接続すると、以前に登録されたすべての監視が再登録され、必要に応じてトリガーされます。一般に、これはすべて透過的に発生します。監視が見逃される可能性があるケースが1つあります。まだ作成されていないznodeの存在に対する監視は、切断中にznodeが作成および削除された場合に見逃されます。

3.6.0の新機能:クライアントは、トリガーされても削除されず、登録されたznodeおよび子znodeの変更を再帰的にトリガーする、永続的な再帰的監視をznodeに設定することもできます。

ウォッチのセマンティクス

ZooKeeperの状態を読み取る3つの呼び出し(exists、getData、getChildren)を使用して監視を設定できます。次のリストは、監視がトリガーできるイベントと、それらを有効にする呼び出しについて詳しく説明します。

永続的な再帰的なウォッチ

3.6.0の新機能:上記で説明した標準の監視には、トリガーされても削除されない監視を設定できるバリエーションが追加されました。さらに、これらの監視は、イベントタイプNodeCreatedNodeDeleted、およびNodeDataChangedをトリガーし、オプションで、監視が登録されているznodeから開始してすべてのznodeを再帰的にトリガーします。 NodeChildrenChangedイベントは冗長になるため、永続的な再帰的監視ではトリガーされないことに注意してください。

永続的な監視は、addWatch()メソッドを使用して設定されます。トリガーのセマンティクスと保証(1回限りのトリガー以外)は、標準の監視と同じです。イベントに関する唯一の例外は、再帰的な永続的な監視が子変更イベントをトリガーしないことです。これは冗長であるためです。永続的な監視は、監視タイプWatcherType.Anyを使用してremoveWatches()を使用して削除されます。

ウォッチの削除

removeWatchesを呼び出すことで、znodeに登録されている監視を削除できます。また、ZooKeeperクライアントは、ローカルフラグをtrueに設定することで、サーバー接続がない場合でもローカルで監視を削除できます。次のリストでは、監視の削除が成功した後にトリガーされるイベントについて詳しく説明します。

ZooKeeper がウォッチに関して保証すること

監視に関して、ZooKeeperは次の保証を維持します。

ウォッチに関して覚えておくべきこと

ACL を使用した ZooKeeper アクセス制御

ZooKeeperは、そのznode(ZooKeeperデータツリーのデータノード)へのアクセスを制御するためにACLを使用します。ACLの実装は、UNIXのファイルアクセス権限とよく似ています。パーミッションビットを使用して、ノードに対するさまざまな操作を許可または禁止し、ビットが適用されるスコープを定義します。標準的なUNIX権限とは異なり、ZooKeeperノードは、ユーザー(ファイルの所有者)、グループ、およびワールド(その他)の3つの標準スコープに限定されません。ZooKeeperにはznodeの所有者の概念はありません。代わりに、ACLは、IDのセットと、それらのIDに関連付けられたパーミッションを指定します。

また、ACLは特定のznodeにのみ関係することに注意してください。特に、子には適用されません。たとえば、/app がip:172.16.16.1によってのみ読み取り可能で、/app/status が誰でも読み取り可能な場合、誰でも /app/status を読み取ることができます。ACLは再帰的ではありません。

ZooKeeperは、プラグ可能な認証スキームをサポートしています。IDは、scheme:expression の形式を使用して指定されます。ここで、scheme はIDが対応する認証スキームです。有効な式は、スキームによって定義されます。たとえば、ip:172.16.16.1 は、ip スキームを使用したアドレス 172.16.16.1 を持つホストのIDであり、digest:bob:password は、digest スキームを使用した名前が bob のユーザーのIDです。

クライアントがZooKeeperに接続して認証されると、ZooKeeperはクライアントの接続に対応するすべてのIDを関連付けます。クライアントがノードにアクセスしようとすると、これらのIDはznodeのACLに対してチェックされます。ACLは、(scheme:expression, perms) のペアで構成されます。expression の形式は、スキームに固有です。たとえば、ペア (ip:19.22.0.0/16, READ) は、19.22で始まるIPアドレスを持つすべてのクライアントにREADパーミッションを付与します。

ACL の権限

ZooKeeperは、以下のパーミッションをサポートしています。

CREATEおよびDELETEパーミッションは、より細かくアクセスを制御するためにWRITEパーミッションから分離されました。CREATEおよびDELETEのケースは次のとおりです。

AがZooKeeperノードで設定を実行できるようにしたいが、子をCREATEまたはDELETEできないようにしたい。

DELETEなしのCREATE:クライアントは、親ディレクトリにZooKeeperノードを作成することにより、リクエストを作成します。すべてのクライアントが追加できるが、リクエストプロセッサのみが削除できるようにしたい。(これは、ファイルのAPPENDパーミッションのようなものです。)

また、ZooKeeperにはファイル所有者の概念がないため、ADMINパーミッションが存在します。ある意味では、ADMINパーミッションはエンティティを所有者として指定します。ZooKeeperは、LOOKUPパーミッション(ディレクトリをリストできない場合でもLOOKUPできるようにするためのディレクトリの実行パーミッションビット)をサポートしていません。誰もが暗黙的にLOOKUPパーミッションを持っています。これにより、ノードの状態を取得できますが、それ以上は何もできません。(問題は、存在しないノードでzoo_exists()を呼び出したい場合、チェックするパーミッションがないことです。)

ADMINパーミッションは、ACLに関して特別な役割も果たします。znodeのACLを取得するには、ユーザーはREADまたはADMINパーミッションを持っている必要がありますが、ADMINパーミッションがない場合、ダイジェストハッシュ値はマスクアウトされます。

バージョン3.9.2 / 3.8.4 / 3.7.3の時点で、exists()呼び出しは、存在するノードのACLを検証するようになり、クライアントはREADパーミッションを持っている必要があります。そうでない場合は、「パーミッションが不十分です」エラーが発生します。

組み込み ACL スキーム

ZooKeeperには、次の組み込みスキームがあります。

ZooKeeper C クライアント API

次の定数は、ZooKeeper Cライブラリによって提供されます。

以下は、標準のACL IDです。

ZOO_AUTH_IDSの空のID文字列は、「作成者のID」と解釈する必要があります。

ZooKeeperクライアントには、3つの標準ACLが付属しています。

ZOO_OPEN_ACL_UNSAFEは、完全にオープンでフリーなすべての人向けのACLです。すべてのアプリケーションは、ノードで任意の操作を実行でき、その子を作成、リスト、および削除できます。ZOO_READ_ACL_UNSAFEは、すべてのアプリケーションの読み取り専用アクセスです。CREATE_ALL_ACLは、ノードの作成者にすべてのパーミッションを付与します。作成者は、このACLでノードを作成する前に、サーバーによって認証されている必要があります(たとえば、「digest」スキームを使用)。

次のZooKeeper操作は、ACLを扱います。

アプリケーションは、zoo_add_auth関数を使用してサーバーに認証を行います。アプリケーションが異なるスキームやIDを使用して認証する場合は、関数を複数回呼び出すことができます。

zoo_create(...)操作は、新しいノードを作成します。aclパラメーターは、ノードに関連付けられたACLのリストです。親ノードには、CREATEパーミッションビットが設定されている必要があります。

この操作は、ノードのACL情報を返します。ノードには、READまたはADMINパーミッションが設定されている必要があります。ADMINパーミッションがない場合、ダイジェストハッシュ値はマスクアウトされます。

この関数は、ノードのACLリストを新しいリストに置き換えます。ノードには、ADMINパーミッションが設定されている必要があります。

以下は、上記のAPIを使用して「foo」スキームで自身を認証し、作成専用パーミッションでエフェメラルノード「/xyz」を作成するサンプルコードです。

注記

これは非常に簡単な例であり、特にZooKeeper ACLと対話する方法を示すことを目的としています。Cクライアントの実装例については、.../trunk/zookeeper-client/zookeeper-client-c/src/cli.c を参照してください。

#include <string.h>
#include <errno.h>

#include "zookeeper.h"

static zhandle_t *zh;

/**
 * In this example this method gets the cert for your
 *   environment -- you must provide
 */
char *foo_get_cert_once(char* id) { return 0; }

/** Watcher function -- empty for this example, not something you should
 * do in real code */
void watcher(zhandle_t *zzh, int type, int state, const char *path,
         void *watcherCtx) {}

int main(int argc, char argv) {
  char buffer[512];
  char p[2048];
  char *cert=0;
  char appId[64];

  strcpy(appId, "example.foo_test");
  cert = foo_get_cert_once(appId);
  if(cert!=0) {
    fprintf(stderr,
        "Certificate for appid [%s] is [%s]\n",appId,cert);
    strncpy(p,cert, sizeof(p)-1);
    free(cert);
  } else {
    fprintf(stderr, "Certificate for appid [%s] not found\n",appId);
    strcpy(p, "dummy");
  }

  zoo_set_debug_level(ZOO_LOG_LEVEL_DEBUG);

  zh = zookeeper_init("localhost:3181", watcher, 10000, 0, 0, 0);
  if (!zh) {
    return errno;
  }
  if(zoo_add_auth(zh,"foo",p,strlen(p),0,0)!=ZOK)
    return 2;

  struct ACL CREATE_ONLY_ACL[] = {{ZOO_PERM_CREATE, ZOO_AUTH_IDS}};
  struct ACL_vector CREATE_ONLY = {1, CREATE_ONLY_ACL};
  int rc = zoo_create(zh,"/xyz","value", 5, &CREATE_ONLY, ZOO_EPHEMERAL,
                  buffer, sizeof(buffer)-1);

  /** this operation will fail with a ZNOAUTH error */
  int buflen= sizeof(buffer);
  struct Stat stat;
  rc = zoo_get(zh, "/xyz", 0, buffer, &buflen, &stat);
  if (rc) {
    fprintf(stderr, "Error %d for %s\n", rc, __LINE__);
  }

  zookeeper_close(zh);
  return 0;
}

プラグ可能な ZooKeeper 認証

ZooKeeperは、さまざまな認証スキームを備えたさまざまな環境で実行されるため、完全にプラグ可能な認証フレームワークを備えています。組み込みの認証スキームでさえ、プラグ可能な認証フレームワークを使用します。

認証フレームワークの仕組みを理解するには、まず2つの主要な認証操作を理解する必要があります。フレームワークは、最初にクライアントを認証する必要があります。これは通常、クライアントがサーバーに接続するとすぐに実行され、クライアントから送信された情報または収集された情報を検証し、それを接続に関連付けることで構成されます。フレームワークによって処理される2番目の操作は、クライアントに対応するACLのエントリを見つけることです。ACLエントリは、<idspec, permissions> ペアです。idspec は、接続に関連付けられた認証情報に対する単純な文字列一致である場合もあれば、その情報に対して評価される式である場合もあります。一致を行うのは、認証プラグインの実装次第です。以下に、認証プラグインが実装する必要があるインターフェイスを示します。

public interface AuthenticationProvider {
    String getScheme();
    KeeperException.Code handleAuthentication(ServerCnxn cnxn, byte authData[]);
    boolean isValid(String id);
    boolean matches(String id, String aclExpr);
    boolean isAuthenticated();
}

最初のメソッド getScheme は、プラグインを識別する文字列を返します。複数の認証方法をサポートしているため、認証資格情報または idspec には常に scheme: がプレフィックスとして付加されます。ZooKeeperサーバーは、認証プラグインによって返されたスキームを使用して、スキームが適用されるIDを決定します。

handleAuthentication は、クライアントが接続に関連付けられる認証情報を送信するときに呼び出されます。クライアントは、情報が対応するスキームを指定します。ZooKeeperサーバーは、getScheme がクライアントによって渡されたスキームに一致する認証プラグインに情報を渡します。handleAuthentication の実装者は、情報が不適切であると判断した場合、通常はエラーを返します。または、cnxn.getAuthInfo().add(new Id(getScheme(), data)) を使用して情報を接続に関連付けます。

認証プラグインは、ACLの設定と使用の両方に関与します。znodeにACLが設定されると、ZooKeeperサーバーは、エントリのID部分を isValid(String id) メソッドに渡します。プラグインは、IDが正しい形式を持っていることを検証する必要があります。たとえば、ip:172.16.0.0/16 は有効なIDですが、ip:host.com は有効ではありません。新しいACLに「auth」エントリが含まれている場合、isAuthenticated を使用して、このスキームの接続に関連付けられている認証情報をACLに追加する必要があるかどうかを確認します。一部のスキームは、authに含めるべきではありません。たとえば、クライアントのIPアドレスは、authが指定されている場合にACLに追加する必要のあるIDとは見なされません。

ZooKeeperは、ACLをチェックする際にmatches(String id, String aclExpr)を呼び出します。これは、クライアントの認証情報を関連するACLエントリと照合する必要があります。クライアントに適用されるエントリを見つけるために、ZooKeeperサーバーは各エントリのスキームを調べ、そのスキームに対するクライアントからの認証情報がある場合、matches(String id, String aclExpr)が呼び出されます。idには、以前にhandleAuthenticationによって接続に追加された認証情報が設定され、aclExprにはACLエントリのidが設定されます。認証プラグインは、独自のロジックとマッチングスキームを使用して、idaclExprに含まれているかどうかを判断します。

組み込みの認証プラグインには、ipdigestの2つがあります。システムプロパティを使用して、追加のプラグインを追加できます。起動時に、ZooKeeperサーバーは「zookeeper.authProvider.」で始まるシステムプロパティを探し、これらのプロパティの値を認証プラグインのクラス名として解釈します。これらのプロパティは、-Dzookeeeper.authProvider.X=com.f.MyAuthを使用するか、サーバー構成ファイルに以下のようなエントリを追加することで設定できます。

authProvider.1=com.f.MyAuth
authProvider.2=com.f.MyAuth2

プロパティの接尾辞が一意であることを確認するために注意が必要です。-Dzookeeeper.authProvider.X=com.f.MyAuth -Dzookeeper.authProvider.X=com.f.MyAuth2のような重複がある場合、1つだけが使用されます。また、すべてのサーバーに同じプラグインが定義されている必要があります。そうでない場合、プラグインによって提供される認証スキームを使用するクライアントは、一部のサーバーへの接続に問題が発生します。

3.6.0で追加:プラグ可能な認証のための代替抽象化が利用可能です。追加の引数を提供します。

public abstract class ServerAuthenticationProvider implements AuthenticationProvider {
    public abstract KeeperException.Code handleAuthentication(ServerObjs serverObjs, byte authData[]);
    public abstract boolean matches(ServerObjs serverObjs, MatchValues matchValues);
}

AuthenticationProviderを実装する代わりに、ServerAuthenticationProviderを拡張します。すると、handleAuthentication()メソッドとmatches()メソッドは、追加のパラメータ(ServerObjsおよびMatchValuesを介して)を受け取ります。

整合性の保証

ZooKeeperは、高性能でスケーラブルなサービスです。読み取り操作と書き込み操作の両方が高速になるように設計されていますが、読み取りの方が書き込みよりも高速です。これは、読み取りの場合、ZooKeeperが古いデータを提供できるためです。これは、ZooKeeperの整合性保証によるものです。

これらの整合性保証を使用すると、リーダー選出、バリア、キュー、および読み取り/書き込み可能な取り消し可能ロックなどの高レベル関数を、ZooKeeperクライアントのみで簡単に構築できます(ZooKeeperへの追加は不要です)。詳細については、レシピとソリューションを参照してください。

注記

開発者が誤ってZooKeeperが実際には行っていない別の保証を想定することがあります。それは、同時一貫性のあるクロスクライアントビューです。ZooKeeperは、常に2つの異なるクライアントがZooKeeperデータの同一ビューを持つことを保証するものではありません。ネットワーク遅延などの要因により、あるクライアントが別のクライアントに変更が通知される前に更新を実行する可能性があります。2つのクライアントAとBのシナリオを考えてみましょう。クライアントAがznode /aの値を0から1に設定し、クライアントBに/aを読み取るように指示した場合、クライアントBは接続しているサーバーによっては、古い値の0を読み取る可能性があります。クライアントAとクライアントBが同じ値を読み取ることが重要な場合、クライアントBは読み取りを実行する前に、ZooKeeper APIメソッドからsync()メソッドを呼び出す必要があります。したがって、ZooKeeper自体は、すべてのサーバー間で変更が同期的に発生することを保証するものではありませんが、ZooKeeperプリミティブを使用して、有用なクライアント同期を提供する高レベル関数を構築できます。(詳細については、ZooKeeperレシピを参照してください。)

バインディング

ZooKeeperクライアントライブラリには、JavaとCの2つの言語があります。次のセクションでは、これらについて説明します。

Java バインディング

ZooKeeper Javaバインディングを構成するパッケージは、org.apache.zookeeperorg.apache.zookeeper.dataの2つです。ZooKeeperを構成する残りのパッケージは、内部的に使用されるか、サーバー実装の一部です。org.apache.zookeeper.dataパッケージは、単にコンテナとして使用される生成されたクラスで構成されています。

ZooKeeper Javaクライアントが使用するメインクラスは、ZooKeeperクラスです。その2つのコンストラクタは、オプションのセッションIDとパスワードのみが異なります。ZooKeeperは、プロセスのインスタンス間でのセッションリカバリをサポートしています。Javaプログラムは、セッションIDとパスワードを安定したストレージに保存し、再起動して、プログラムの以前のインスタンスで使用されていたセッションを回復できます。

ZooKeeperオブジェクトが作成されると、IOスレッドとイベントスレッドの2つのスレッドも作成されます。すべてのIOはIOスレッド(Java NIOを使用)で発生します。すべてのイベントコールバックはイベントスレッドで発生します。ZooKeeperサーバーへの再接続やハートビートの維持などのセッションメンテナンスは、IOスレッドで行われます。同期メソッドのレスポンスもIOスレッドで処理されます。非同期メソッドおよびウォッチイベントへのすべてのレスポンスは、イベントスレッドで処理されます。この設計から生じるいくつかの注意点があります。

最後に、シャットダウンに関連するルールは簡単です。ZooKeeperオブジェクトが閉じられるか、致命的なイベント(SESSION_EXPIREDとAUTH_FAILED)を受信すると、ZooKeeperオブジェクトは無効になります。クローズすると、2つのスレッドがシャットダウンし、zookeeperハンドルへのそれ以降のアクセスは未定義の動作であり、回避する必要があります。

クライアント構成パラメータ

次のリストには、Javaクライアントの設定プロパティが含まれています。これらのプロパティは、Javaシステムプロパティを使用して設定できます。サーバープロパティについては、管理ガイドのサーバー構成セクションを確認してください。ZooKeeper Wikiには、ZooKeeper SSLサポートZooKeeperのSASL認証に関する役立つページもあります。

C バインディング

Cバインディングには、シングルスレッドライブラリとマルチスレッドライブラリがあります。マルチスレッドライブラリは最も使いやすく、Java APIに最もよく似ています。このライブラリは、接続のメンテナンスとコールバックを処理するためのIOスレッドとイベントディスパッチスレッドを作成します。シングルスレッドライブラリでは、マルチスレッドライブラリで使用されているイベントループを公開することで、イベントドリブンアプリケーションでZooKeeperを使用できます。

パッケージには、zookeeper_stとzookeeper_mtの2つの共有ライブラリが含まれています。前者は、アプリケーションのイベントループに統合するための非同期APIとコールバックのみを提供します。このライブラリが存在する唯一の理由は、pthreadライブラリが利用できないか不安定なプラットフォーム(つまりFreeBSD 4.x)をサポートするためです。それ以外の場合は、すべてのアプリケーション開発者は、同期APIと非同期APIの両方のサポートが含まれているため、zookeeper_mtとリンクする必要があります。

インストール

Apacheリポジトリからチェックアウトしてクライアントをビルドする場合は、以下に示す手順に従ってください。Apacheからダウンロードしたプロジェクトソースパッケージからビルドする場合は、手順3にスキップしてください。

  1. zookeeper-juteディレクトリ(.../trunk/zookeeper-jute)でmvn compileを実行します。これにより、.../trunk/zookeeper-client/zookeeper-client-cの下に「generated」という名前のディレクトリが作成されます。
  2. ディレクトリを*.../trunk/zookeeper-client/zookeeper-client-c*に変更し、autoreconf -ifを実行してautoconfautomake、およびlibtoolをブートストラップします。autoconfバージョン2.59以上がインストールされていることを確認してください。手順4にスキップします。
  3. プロジェクトソースパッケージからビルドする場合は、ソースtarballを解凍/展開し、*zookeeper-x.x.x/zookeeper-client/zookeeper-client-c*ディレクトリにcdします。
  4. ./configure <your-options>を実行してmakefileを生成します。以下は、このステップで役立つ可能性のあるconfigureユーティリティがサポートするオプションの一部です。
注記

configureの実行に関する一般的な情報については、INSTALLを参照してください。1. makeまたはmake installを実行してライブラリをビルドしてインストールします。2. ZooKeeper APIのdoxygenドキュメントを生成するには、make doxygen-docを実行します。すべてのドキュメントは、docsという名前の新しいサブフォルダーに配置されます。デフォルトでは、このコマンドはHTMLのみを生成します。他のドキュメント形式については、./configure --helpを実行してください。

独自の C クライアントの構築

アプリケーションでZooKeeper C APIを使用できるようにするには、次のことを覚えておく必要があります。

  1. ZooKeeperヘッダーを含める:#include <zookeeper/zookeeper.h>
  2. マルチスレッドクライアントをビルドする場合は、-DTHREADEDコンパイラフラグを使用して、マルチスレッドバージョンのライブラリを有効にし、次にzookeeper_mtライブラリとリンクします。シングルスレッドクライアントをビルドする場合は、-DTHREADEDでコンパイルしないでください。必ず_zookeeper_st_libraryとリンクしてください。
注記

Cクライアントの実装例については、.../trunk/zookeeper-client/zookeeper-client-c/src/cli.cを参照してください。

ビルディングブロック:ZooKeeper 操作ガイド

このセクションでは、開発者がZooKeeperサーバーに対して実行できるすべての操作について概説します。これは、このマニュアルの以前の概念の章よりも低レベルの情報ですが、ZooKeeper APIリファレンスよりも高レベルです。次のトピックについて説明します。

エラー処理

JavaとCのクライアントバインディングの両方でエラーを報告する可能性があります。JavaクライアントバインディングはKeeperExceptionをスローすることでこれを行い、例外でcode()を呼び出すと特定のエラーコードが返されます。Cクライアントバインディングは、enum ZOO_ERRORSで定義されているエラーコードを返します。APIコールバックは、両方の言語バインディングの結果コードを示します。可能なエラーとその意味の詳細については、APIドキュメント(Javaの場合はjavadoc、Cの場合はdoxygen)を参照してください。

ZooKeeper への接続

始める前に、クライアントの開発を開始できるように、実行中のZookeeperサーバーを設定する必要があります。Cクライアントバインディングの場合、Cで記述された簡単な例でマルチスレッドライブラリ(zookeeper_mt)を使用します。Zookeeperサーバーとの接続を確立するために、次の署名を持つC API - zookeeper_initを使用します。

int zookeeper_init(const char *host, watcher_fn fn, int recv_timeout, const clientid_t *clientid, void *context, int flags);

接続に成功した場合は「Zookeeperに接続しました」、それ以外の場合はエラーメッセージを出力するクライアントをデモンストレーションします。次のコードをzkClient.ccと呼びましょう。

#include <stdio.h>
#include <zookeeper/zookeeper.h>
#include <errno.h>
using namespace std;

// Keeping track of the connection state
static int connected = 0;
static int expired   = 0;

// *zkHandler handles the connection with Zookeeper
static zhandle_t *zkHandler;

// watcher function would process events
void watcher(zhandle_t *zkH, int type, int state, const char *path, void *watcherCtx)
{
    if (type == ZOO_SESSION_EVENT) {

        // state refers to states of zookeeper connection.
        // To keep it simple, we would demonstrate these 3: ZOO_EXPIRED_SESSION_STATE, ZOO_CONNECTED_STATE, ZOO_NOTCONNECTED_STATE
        // If you are using ACL, you should be aware of an authentication failure state - ZOO_AUTH_FAILED_STATE
        if (state == ZOO_CONNECTED_STATE) {
            connected = 1;
        } else if (state == ZOO_NOTCONNECTED_STATE ) {
            connected = 0;
        } else if (state == ZOO_EXPIRED_SESSION_STATE) {
            expired = 1;
            connected = 0;
            zookeeper_close(zkH);
        }
    }
}

int main(){
    zoo_set_debug_level(ZOO_LOG_LEVEL_DEBUG);

    // zookeeper_init returns the handler upon a successful connection, null otherwise
    zkHandler = zookeeper_init("localhost:2181", watcher, 10000, 0, 0, 0);

    if (!zkHandler) {
        return errno;
    }else{
        printf("Connection established with Zookeeper. \n");
    }

    // Close Zookeeper connection
    zookeeper_close(zkHandler);

    return 0;
}

前に説明したマルチスレッドライブラリでコードをコンパイルします。

> g++ -Iinclude/ zkClient.cpp -lzookeeper_mt -o Client

クライアントを実行します。

> ./Client

出力から、接続が成功した場合は、ZooKeeperのDEBUGメッセージとともに「Zookeeperに接続しました」が表示されるはずです。

落とし穴:一般的な問題とトラブルシューティング

これでZooKeeperがわかりました。高速でシンプルで、アプリケーションは動作しますが、ちょっと待ってください...何か問題があります。以下は、ZooKeeperユーザーが陥る可能性のある落とし穴の一部です。

  1. ウォッチを使用している場合は、接続済みウォッチイベントを探す必要があります。ZooKeeperクライアントがサーバーから切断されると、再接続されるまで変更の通知は受信されません。znodeの出現を監視している場合は、切断中にznodeが作成および削除されると、イベントを見逃します。
  2. ZooKeeperサーバーの障害をテストする必要があります。ZooKeeperサービスは、サーバーの過半数がアクティブである限り、障害を生き残ることができます。問うべき質問は、アプリケーションがそれを処理できるかどうかです。現実の世界では、クライアントのZooKeeperへの接続が切断される可能性があります。(ZooKeeperサーバーの障害やネットワークパーティションは、接続が失われる一般的な理由です。)ZooKeeperクライアントライブラリは、接続の回復と発生した内容の通知を処理しますが、状態と失敗した未処理のリクエストを回復する必要があります。テストラボで正しく実行できたかどうかを確認し、実稼働環境では確認しないようにしてください。複数のサーバーで構成され、再起動の影響を受けるZooKeeperサービスでテストしてください。
  3. クライアントが使用するZooKeeperサーバーのリストは、各ZooKeeperサーバーが持つZooKeeperサーバーのリストと一致する必要があります。クライアントリストがZooKeeperサーバーの実際のリストのサブセットである場合は(最適ではありませんが)機能しますが、クライアントがZooKeeperクラスターにないZooKeeperサーバーをリストしている場合は機能しません。
  4. トランザクションログを配置する場所に注意してください。ZooKeeperの最もパフォーマンスが重要な部分は、トランザクションログです。ZooKeeperは、応答を返す前にトランザクションをメディアに同期する必要があります。専用のトランザクションログデバイスは、一貫した良好なパフォーマンスを実現するための鍵となります。ログをビジー状態のデバイスに配置すると、パフォーマンスに悪影響を及ぼします。ストレージデバイスが1つしかない場合は、トレースファイルをNFSに配置し、snapshotCountを増やしてください。問題は解消されませんが、緩和することができます。
  5. Javaの最大ヒープサイズを正しく設定してください。スワッピングを避けることが非常に重要です。不必要にディスクにアクセスすると、ほぼ確実にパフォーマンスが許容できないほど低下します。ZooKeeperでは、すべてが順序付けられているため、1つのリクエストがディスクにヒットすると、キューに入れられた他のすべてのリクエストがディスクにヒットすることを忘れないでください。スワッピングを回避するには、ヒープサイズを物理メモリの量から、OSとキャッシュに必要な量を引いた値に設定してみてください。構成に最適なヒープサイズを決定する最良の方法は、負荷テストを実行することです。何らかの理由でそれができない場合は、見積もりを控えめにして、マシンがスワップを引き起こす制限を十分に下回る数値を選択してください。たとえば、4Gマシンの場合、3Gヒープは最初に始めるための控えめな見積もりです。

その他の情報へのリンク

公式ドキュメント以外にも、ZooKeeperの開発者向けの有益な情報源がいくつかあります。