Apache > ZooKeeper
 

ZooKeeper プログラマーガイド

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

はじめに

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

このガイドの最初の4つのセクションでは、さまざまな ZooKeeper の概念に関するより高度な議論を提示します。これらは、ZooKeeper の動作方法と使用方法の両方の理解に必要です。ソースコードは含まれていませんが、分散コンピューティングに関連する問題に関する知識があることを前提としています。この最初のグループのセクションは次のとおりです。

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

このドキュメントは、付録で、ZooKeeper 関連のその他の有用な情報へのリンクを含めて締めくくられています。

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

ZooKeeper データモデル

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

Zノード

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

注記

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

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

ウォッチ

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

データアクセス

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

ZooKeeper は、汎用データベースや大規模オブジェクトストアとして設計されていません。代わりに、調整データを管理します。このデータは、構成、ステータス情報、ランデブーなどの形式で提供できます。さまざまな形式の調整データの共通の特性は、比較的少ないことです。キロバイト単位で測定されます。ZooKeeper クライアントとサーバーの実装には、zノードのデータが 1MB 未満であることを確認するためのサニティチェックがありますが、データは平均してそれよりもはるかに少ないはずです。比較的大きなデータサイズを操作すると、一部の操作にかかる時間が他の操作よりもはるかに長くなり、より多くのデータをネットワーク経由でストレージメディアに移動するのに必要な追加時間のために、一部の操作のレイテンシに影響します。大規模なデータストレージが必要な場合は、NFS や HDFS などのバルクストレージシステムにデータを格納し、ZooKeeper にストレージ場所へのポインタを格納するのが一般的なパターンです。

一時ノード

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

getEphemerals()

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

シーケンスノード — 一意の命名

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

コンテナノード

3.5.3 で追加

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

このプロパティを考えると、コンテナ zノード内に子を作成するときに KeeperException.NoNodeException が発生する準備をする必要があります。つまり、コンテナ zノード内に子 zノードを作成するときは、常に KeeperException.NoNodeException を確認し、発生した場合はコンテナ zノードを再作成します。

TTL ノード

3.5.3 で追加

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

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

ZooKeeperにおける時間

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

ZooKeeper Stat 構造体

ZooKeeper の各 zノードの Stat 構造体は、次のフィールドで構成されています。

ZooKeeper セッション

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

State transitions

クライアントセッションを作成するには、アプリケーションコードは、ZooKeeperサーバーに対応するホスト:ポートのペアをコンマで区切ったリストを含む接続文字列を提供する必要があります(例:「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を送信します。セキュリティ対策として、サーバーはセッションIDのパスワードを作成し、ZooKeeperサーバーはそれを検証できます。パスワードは、クライアントがセッションを確立するときに、セッションIDとともにクライアントに送信されます。クライアントは、新しいサーバーとセッションを再確立するたびに、このパスワードをセッションIDとともに送信します。

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

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

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

期限切れセッションのウォッチャーから見た、期限切れセッションの状態遷移の例

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

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

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

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

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

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

サーバーリストの更新。コンマで区切ったホスト:ポートのペアの新しいリストを提供することにより、クライアントが接続文字列を更新できるようにします。各ペアはZooKeeperサーバーに対応します。この関数は確率的ロードバランシングアルゴリズムを呼び出し、新しいリスト内のサーバーごとに予想される均一な接続数を達成することを目的として、クライアントが現在のホストから切断される可能性があります。クライアントが接続されている現在のホストが新しいリストにない場合、この呼び出しは常に接続が切断される原因となります。それ以外の場合は、サーバーの数が増加したか減少したか、その程度によって決定されます。

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

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

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

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

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

localSessionsUpgradingEnabled が有効な場合

ZooKeeper ウォッチ

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

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

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

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

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

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

3.6.0の新機能:上記で説明した標準的なウォッチのバリエーションがあり、トリガーされたときに削除されないウォッチを設定できます。さらに、これらのウォッチは、NodeCreatedNodeDeletedNodeDataChangedというイベントタイプをトリガーし、オプションで、ウォッチが登録されているznodeから始まるすべてのznodeに対して再帰的にトリガーします。永続的な再帰的なウォッチでは、NodeChildrenChangedイベントは冗長であるためトリガーされません。

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

ウォッチの削除

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

ZooKeeper がウォッチについて保証すること

ウォッチに関して、ZooKeeperはこれらの保証を維持します。

ウォッチに関する留意事項

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

ZooKeeperは、ACLを使用して、そのznode(ZooKeeperデータツリーのデータノード)へのアクセスを制御します。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は、次の権限をサポートしています。

より詳細なアクセス制御のために、WRITE パーミッションからCREATEDELETE パーミッションが分割されました。CREATEDELETE の使用例は以下の通りです。

ZooKeeper ノードに対して A が SET 操作を実行できるようにしたいが、子ノードのCREATEDELETE は許可したくない場合。

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

また、ZooKeeper にファイル所有者の概念がないため、ADMIN パーミッションが存在します。ある意味、ADMIN パーミッションはエンティティを所有者として指定します。ZooKeeper は LOOKUP パーミッション(ディレクトリをリスト表示できなくても LOOKUP を許可するディレクトリの execute パーミッションビット)をサポートしていません。すべてのユーザーは暗黙的に LOOKUP パーミッションを持っています。これにより、ノードの状態を確認できますが、それ以上はできません。(問題は、存在しないノードに対して `zoo_exists()` を呼び出したい場合、確認するためのパーミッションがないことです。)

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

組み込み 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 パーミッションが設定されている必要があります。

以下は、「foo」スキームを使用して自身を認証し、作成のみの権限を持つ一時ノード「/xyz」を作成する上記の API を使用するサンプルコードです。

注記

これは、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`は、接続に関連付けられた認証情報に対する単純な文字列一致である場合もあれば、その情報に対して評価される式である場合もあります。一致を行うのは認証プラグインの実装次第です。認証プラグインが実装する必要があるインターフェースを次に示します。

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 としては考慮されません。

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

組み込みの認証プラグインには、`ip` と `digest` の 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データの同一のビューを持つことを保証しません。ネットワーク遅延などの要因により、あるクライアントが更新を実行する前に、別のクライアントが変更の通知を受け取る場合があります。クライアントAとクライアントBのシナリオを考えてみましょう。クライアントAがzノード/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`を実行して**autoconf**、**automake**、**libtool**をブートストラップします。**autoconf バージョン2.59以上**がインストールされていることを確認してください。ステップ**4**に進みます。
  3. プロジェクトソースパッケージからビルドする場合は、ソースターボールを解凍/展開し、`zookeeper-x.x.x/zookeeper-client/zookeeper-client-c`ディレクトリに移動します。
  4. `./configure `を実行してMakefileを生成します。このステップで役立つ、**configure**ユーティリティがサポートするオプションの一部を以下に示します。
注記

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

独自の C クライアントのビルド

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

  1. ZooKeeperヘッダーを含める: `#include `
  2. マルチスレッドクライアントをビルドする場合は、`-DTHREADED`コンパイラフラグを使用してライブラリのマルチスレッドバージョンを有効にしてコンパイルし、`zookeeper_mt`ライブラリにリンクします。シングルスレッドクライアントをビルドする場合は、`-DTHREADED`を使用せずにコンパイルし、`zookeeper_st`ライブラリにリンクするようにしてください。
注記

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に接続しました」とZookeeperのデバッグメッセージが表示されます。

注意点:よくある問題とトラブルシューティング

これでZooKeeperについて理解できました。高速でシンプルで、アプリケーションは動作しますが、待ってください…何かが間違っています。ZooKeeperユーザーが陥りがちな落とし穴をいくつか紹介します。

  1. ウォッチを使用している場合は、接続されたウォッチイベントを確認する必要があります。ZooKeeperクライアントがサーバーから切断されると、再接続するまで変更の通知は受信されません。zノードの存在を監視している場合、切断中にzノードが作成および削除されると、そのイベントを見逃します。
  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とキャッシュに必要な量を引いた物理メモリの量に設定してみてください。構成に対して最適なヒープサイズを決定する最良の方法は、*負荷テストを実行する*ことです。何らかの理由で実行できない場合は、見積もりを控えめにし、マシンにスワップが発生する可能性のある制限をはるかに下回る数値を選択してください。たとえば、4GBのマシンでは、3GBのヒープは保守的な見積もりです。

その他の情報のリンク

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