ZooKeeper プログラマーズガイド
ZooKeeper を使用した分散アプリケーションの開発
- はじめに
- ZooKeeper データモデル
- ZooKeeper セッション
- ZooKeeper ウォッチ
- ACL を使用した ZooKeeper アクセス制御
- プラグ可能な ZooKeeper 認証
- 整合性の保証
- バインディング
- ビルディングブロック:ZooKeeper 操作ガイド
- 落とし穴:一般的な問題とトラブルシューティング
はじめに
このドキュメントは、ZooKeeper の調整サービスを利用した分散アプリケーションを作成したい開発者向けのガイドです。概念的な情報と実践的な情報が含まれています。
このガイドの最初の 4 つのセクションでは、さまざまな ZooKeeper の概念について高レベルで説明します。これらは、ZooKeeper がどのように機能するか、およびそれを使用する方法を理解するために必要です。ソースコードは含まれていませんが、分散コンピューティングに関連する問題に精通していることを前提としています。この最初のグループのセクションは次のとおりです。
次の 4 つのセクションでは、実践的なプログラミング情報を提供します。これらは次のとおりです。
この本は、その他の役立つ ZooKeeper 関連情報へのリンクを含む付録で締めくくられます。
このドキュメントの情報のほとんどは、スタンドアロンの参考資料としてアクセスできるように記述されています。ただし、最初の ZooKeeper アプリケーションを開始する前に、少なくともZooKeeper データモデルとZooKeeper 基本操作の章を読むことをお勧めします。
ZooKeeper データモデル
ZooKeeper には、分散ファイルシステムによく似た階層的な名前空間があります。唯一の違いは、名前空間内の各ノードに子だけでなくデータも関連付けることができることです。ファイルがディレクトリにもなれるファイルシステムのようなものです。ノードへのパスは常に、正規化された絶対スラッシュ区切りのパスとして表現されます。相対参照はありません。次の制約に従って、パスには任意のユニコード文字を使用できます。
- ヌル文字(\u0000)はパス名の一部にすることはできません(これは C バインディングで問題を引き起こします)。
- 次の文字は、適切に表示されないか、紛らわしい方法でレンダリングされるため、使用できません:\u0001 - \u001F および \u007F
- \u009F。
- 次の文字は許可されていません:\ud800 - uF8FF、\uFFF0 - uFFFF。
- 「.」文字は別の名前の一部として使用できますが、「.」および「..」を単独で使用してパス上のノードを示すことはできません。これは、ZooKeeper が相対パスを使用しないためです。次のようなものは無効です: "/a/b/./c" または "/a/b/../c"。
- トークン「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 桁 (カウンターはソートを簡単にするためにこの形式でフォーマットされます)、つまり"
コンテナノード
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 は複数の方法で時間を追跡します
- Zxid ZooKeeper の状態に対するすべての変更は、zxid (ZooKeeper トランザクション ID) の形式でスタンプを受け取ります。これにより、ZooKeeper に対するすべての変更の合計順序が公開されます。各変更には一意の zxid があり、zxid1 が zxid2 より小さい場合、zxid1 は zxid2 より前に発生しました。
- バージョン番号ノードへの変更はすべて、そのノードのバージョン番号の 1 つを増加させます。3 つのバージョン番号は、バージョン(znode のデータへの変更回数)、cversion(znode の子への変更回数)、および aversion(znode の ACL への変更回数)です。
- ティック マルチサーバーの ZooKeeper を使用する場合、サーバーはステータスのアップロード、セッションタイムアウト、ピア間の接続タイムアウトなどのイベントのタイミングを定義するためにティックを使用します。ティック時間は、最小セッションタイムアウト(ティック時間の2倍)を通じて間接的にのみ公開されます。クライアントが最小セッションタイムアウトよりも短いセッションタイムアウトを要求した場合、サーバーはクライアントにセッションタイムアウトが実際には最小セッションタイムアウトであることを伝えます。
- 実時間 ZooKeeper は、znode の作成時および znode の変更時に stat 構造にタイムスタンプを入れる場合を除いて、実時間、つまりクロック時間を使用しません。
ZooKeeper Stat 構造
ZooKeeper の各 znode の Stat 構造は、以下のフィールドで構成されています。
- czxid この znode が作成された原因となった変更の zxid。
- mzxid この znode を最後に変更した変更の zxid。
- pzxid この znode の子を最後に変更した変更の zxid。
- ctime この znode が作成されたエポックからのミリ秒単位の時間。
- mtime この znode が最後に変更されたエポックからのミリ秒単位の時間。
- version この znode のデータに対する変更回数。
- cversion この znode の子に対する変更回数。
- aversion この znode の ACL に対する変更回数。
- ephemeralOwner この znode がエフェメラルノードの場合、この znode の所有者のセッション ID。エフェメラルノードでない場合はゼロになります。
- dataLength この znode のデータフィールドの長さ。
- numChildren この znode の子の数。
ZooKeeper セッション
ZooKeeper クライアントは、言語バインディングを使用してサービスへのハンドルを作成することにより、ZooKeeper サービスとのセッションを確立します。作成されると、ハンドルは最初に CONNECTING 状態になり、クライアントライブラリは ZooKeeper サービスを構成するサーバーのいずれかに接続しようとします。この時点で CONNECTED 状態に切り替わります。通常の動作中、クライアントハンドルはこれらの2つの状態のいずれかになります。セッションの有効期限切れや認証の失敗など、回復不能なエラーが発生した場合、またはアプリケーションが明示的にハンドルを閉じた場合、ハンドルは CLOSED 状態に移行します。次の図は、ZooKeeper クライアントの可能な状態遷移を示しています。
クライアントセッションを作成するには、アプリケーションコードは、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接続が再確立されるまで切断状態のままであり、その時点で、期限切れセッションのウォッチャーは「セッションの有効期限切れ」通知を受信します。
期限切れセッションのウォッチャーによって認識される、期限切れセッションの状態遷移の例
- 「接続済み」:セッションが確立され、クライアントはクラスターと通信しています(クライアント/サーバー間の通信は正常に動作しています)。
- ...クライアントはクラスターから分割されます
- 「切断済み」:クライアントはクラスターとの接続を失いました
- ...時間が経過し、「タイムアウト」期間の後、クラスターはセッションを期限切れにします。クライアントはクラスターから切断されているため、何も表示されません
- ...時間が経過し、クライアントはクラスターとのネットワークレベルの接続を取り戻します
- 「期限切れ」:最終的にクライアントはクラスターに再接続し、その後、期限切れが通知されます
ZooKeeper セッション確立呼び出しへの別のパラメータは、デフォルトのウォッチャーです。ウォッチャーは、クライアントで状態の変化が発生すると通知されます。たとえば、クライアントがサーバーへの接続を失った場合、クライアントは通知されます。また、クライアントのセッションの有効期限が切れた場合などにも通知されます。このウォッチャーは、初期状態が切断されていると見なす必要があります(つまり、クライアントライブラリからウォッチャーに状態変更イベントが送信される前)。新しい接続の場合、ウォッチャーに送信される最初のイベントは通常、セッション接続イベントです。
セッションは、クライアントによって送信されたリクエストによって維持されます。セッションがタイムアウトする時間だけアイドル状態になっている場合、クライアントはセッションを維持するためにPINGリクエストを送信します。このPINGリクエストにより、ZooKeeperサーバーはクライアントがまだアクティブであることを知ることができるだけでなく、クライアントはZooKeeperサーバーへの接続がまだアクティブであることを確認することもできます。PINGのタイミングは、デッド接続を検出し、新しいサーバーに再接続するための妥当な時間を確保するのに十分なほど保守的です。
サーバーへの接続が正常に確立される(接続される)と、クライアントライブラリがconnectionloss(cバインディングの結果コード、Javaの例外--バインディング固有の詳細についてはAPIドキュメントを参照してください)を生成する基本的には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によって実装されています。
- 背景:セッションの作成とクローズは、クォーラム確認が必要なため、ZooKeeperではコストがかかり、数千のクライアント接続を処理する必要がある場合、ZooKeeperアンサンブルのボトルネックになります。そのため、3.5.0以降では、通常の(グローバル)セッションの完全な機能を持たない新しいタイプのセッションであるローカルセッションを導入します。この機能は、localSessionsEnabledをオンにすることで利用可能になります。
localSessionsUpgradingEnabledが無効になっている場合
-
ローカルセッションは一時的なノードを作成できません
-
ローカルセッションが失われると、ユーザーはセッションID/パスワードを使用して再確立することはできません。セッションとその監視は完全に失われます。注:TCP接続の喪失は、必ずしもセッションが失われることを意味するわけではありません。セッションタイムアウトの前に、同じzkサーバーで接続を再確立できる場合、クライアントは続行できます(別のサーバーに移動することはできません)。
-
ローカルセッションが接続すると、セッション情報は、接続されているZooKeeperサーバー上でのみ維持されます。リーダーはそのようなセッションの作成を認識せず、ディスクに書き込まれる状態もありません。
-
ping、有効期限、その他のセッション状態の維持は、現在のセッションが接続されているサーバーによって処理されます。
localSessionsUpgradingEnabledが有効になっている場合
-
ローカルセッションは、自動的にグローバルセッションにアップグレードできます。
-
新しいセッションが作成されると、ラップされたLocalSessionTrackerにローカルに保存されます。その後、必要に応じて(たとえば、一時的なノードを作成するために)グローバルセッションにアップグレードできます。アップグレードが要求された場合、セッションは同じセッションIDを保持しながら、ローカルコレクションから削除されます。
-
現在、ローカルからグローバルへのセッションアップグレードが必要な操作は、一時的なノードを作成する操作のみです。その理由は、一時的なノードの作成がグローバルセッションに大きく依存しているためです。ローカルセッションがグローバルセッションにアップグレードせずに一時的なノードを作成できる場合、異なるノード間でデータの一貫性がなくなる可能性があります。リーダーは、クローズ/有効期限切れ時に一時的なノードをクリーンアップするために、セッションのライフスパンについても知っておく必要があります。これには、ローカルセッションが特定のサーバーに結び付けられているため、グローバルセッションが必要です。
-
セッションはアップグレード中にローカルセッションとグローバルセッションの両方になることができますが、アップグレードの操作は2つのスレッドで同時に呼び出すことはできません。
-
ZooKeeperServer(スタンドアロン)はSessionTrackerImplを使用します。LeaderZookeeperは、SessionTrackerImpl(グローバル)とLocalSessionTracker(有効な場合)を保持するLeaderSessionTrackerを使用します。FollowerZooKeeperServerとObserverZooKeeperServerは、LocalSessionTrackerを保持するLearnerSessionTrackerを使用します。セッションに関するクラスのUMLグラフ
+----------------+ +--------------------+ +---------------------+ | | --> | | ----> | LocalSessionTracker | | SessionTracker | | SessionTrackerImpl | +---------------------+ | | | | +-----------------------+ | | | | +-------------------------> | LeaderSessionTracker | +----------------+ +--------------------+ | +-----------------------+ | | | | | | | +---------------------------+ +---------> | | | UpgradeableSessionTracker | | | | | ------------------------+ +---------------------------+ | | | v +-----------------------+ | LearnerSessionTracker | +-----------------------+
-
Q&A
- ローカルセッションのアップグレードを無効にするための構成オプションがある理由は何ですか?
- 非常に多数のクライアントを処理したい大規模なデプロイメントでは、オブザーバーを介して接続するクライアントはローカルセッションのみであることが想定されていることを知っています。したがって、これは誰かが誤って多数の一時的なノードとグローバルセッションを作成するのを防ぐための安全策のようなものです。
-
セッションはいつ作成されますか?
- 現在の実装では、ConnectRequestを処理するとき、およびcreateSessionリクエストがFinalRequestProcessorに到達したときに、ローカルセッションを作成しようとします。
-
セッションの作成がサーバーAで送信され、クライアントが別のサーバーBに切断され、再度送信され、切断されてサーバーAに戻るとどうなりますか?
- クライアントがBに再接続すると、そのsessionIdはBのローカルセッショントラッカーに存在しません。したがって、Bは検証パケットを送信します。Aによって発行されたCreateSessionが検証パケットの到着前にコミットされた場合、クライアントは接続できます。それ以外の場合、クライアントはセッションの有効期限が切れます。これは、クォーラムがこのセッションについてまだ認識していないためです。クライアントがAに再度接続しようとすると、セッションはローカルセッショントラッカーからすでに削除されています。したがって、Aはリーダーに検証パケットを送信する必要があります。結果は、リクエストのタイミングに応じて、Bと同じになるはずです。
ZooKeeper ウォッチ
ZooKeeperでのすべての読み取り操作 - getData()、getChildren()、およびexists() - には、副作用として監視を設定するオプションがあります。以下は、ZooKeeperの監視の定義です。監視イベントとは、監視を設定したクライアントに送信される1回限りのトリガーであり、監視が設定されたデータの変更が発生すると発生します。この監視の定義では、考慮すべき3つの重要なポイントがあります。
- 1回限りのトリガー データが変更されると、1つの監視イベントがクライアントに送信されます。たとえば、クライアントがgetData("/znode1", true)を実行し、その後/znode1のデータが変更または削除された場合、クライアントは/znode1の監視イベントを取得します。 /znode1が再度変更された場合、クライアントが新しい監視を設定する別の読み取りを実行しない限り、監視イベントは送信されません。
- クライアントに送信される これは、イベントがクライアントに送信されている途中であることを意味しますが、変更操作への正常なリターンコードが変更を開始したクライアントに到達する前にクライアントに到達しない可能性があることを意味します。監視は非同期で監視者に送信されます。 ZooKeeperは順序保証を提供します。クライアントは、監視イベントを最初に確認するまで、監視を設定した変更を確認することはありません。ネットワーク遅延またはその他の要因により、異なるクライアントが監視と更新からのリターンコードを異なるタイミングで確認する可能性があります。重要な点は、異なるクライアントによって見られるすべてのものが一貫した順序になるということです。
- 監視が設定されたデータ これは、ノードが変更できるさまざまな方法を指します。 ZooKeeperは、データの監視と子の監視の2つの監視リストを維持していると考えると役立ちます。 getData()とexists()はデータの監視を設定します。 getChildren()は子の監視を設定します。あるいは、返されるデータの種類に応じて監視が設定されていると考えると役立つかもしれません。 getData()とexists()は、ノードのデータに関する情報を返し、getChildren()は子のリストを返します。したがって、setData()は、設定されているznodeのデータの監視をトリガーします(設定が成功した場合)。成功したcreate()は、作成中のznodeのデータ監視と、親znodeの子監視をトリガーします。成功したdelete()は、削除されているznodeのデータ監視と子監視の両方(子が存在できなくなるため)と、親znodeの子監視をトリガーします。
監視は、クライアントが接続されているZooKeeperサーバーでローカルに維持されます。これにより、監視を軽量に設定、維持、およびディスパッチできます。クライアントが新しいサーバーに接続すると、セッションイベントの監視がトリガーされます。サーバーから切断されている間、監視は受信されません。クライアントが再接続すると、以前に登録されたすべての監視が再登録され、必要に応じてトリガーされます。一般に、これはすべて透過的に発生します。監視が見逃される可能性があるケースが1つあります。まだ作成されていないznodeの存在に対する監視は、切断中にznodeが作成および削除された場合に見逃されます。
3.6.0の新機能:クライアントは、トリガーされても削除されず、登録されたznodeおよび子znodeの変更を再帰的にトリガーする、永続的な再帰的監視をznodeに設定することもできます。
ウォッチのセマンティクス
ZooKeeperの状態を読み取る3つの呼び出し(exists、getData、getChildren)を使用して監視を設定できます。次のリストは、監視がトリガーできるイベントと、それらを有効にする呼び出しについて詳しく説明します。
- 作成済みイベント:existsの呼び出しで有効になります。
- 削除済みイベント:exists、getData、getChildrenの呼び出しで有効になります。
- 変更済みイベント:existsおよびgetDataの呼び出しで有効になります。
- 子イベント:getChildrenの呼び出しで有効になります。
永続的な再帰的なウォッチ
3.6.0の新機能:上記で説明した標準の監視には、トリガーされても削除されない監視を設定できるバリエーションが追加されました。さらに、これらの監視は、イベントタイプNodeCreated、NodeDeleted、およびNodeDataChangedをトリガーし、オプションで、監視が登録されているznodeから開始してすべてのznodeを再帰的にトリガーします。 NodeChildrenChangedイベントは冗長になるため、永続的な再帰的監視ではトリガーされないことに注意してください。
永続的な監視は、addWatch()メソッドを使用して設定されます。トリガーのセマンティクスと保証(1回限りのトリガー以外)は、標準の監視と同じです。イベントに関する唯一の例外は、再帰的な永続的な監視が子変更イベントをトリガーしないことです。これは冗長であるためです。永続的な監視は、監視タイプWatcherType.Anyを使用してremoveWatches()を使用して削除されます。
ウォッチの削除
removeWatchesを呼び出すことで、znodeに登録されている監視を削除できます。また、ZooKeeperクライアントは、ローカルフラグをtrueに設定することで、サーバー接続がない場合でもローカルで監視を削除できます。次のリストでは、監視の削除が成功した後にトリガーされるイベントについて詳しく説明します。
- 子削除イベント:getChildrenの呼び出しで追加された監視。
- データ削除イベント:existsまたはgetDataの呼び出しで追加された監視。
- 永続的な削除イベント:永続的な監視を追加するための呼び出しで追加された監視。
ZooKeeper がウォッチに関して保証すること
監視に関して、ZooKeeperは次の保証を維持します。
-
監視は、他のイベント、他の監視、および非同期応答に関して順序付けられます。 ZooKeeperクライアントライブラリは、すべてが順番にディスパッチされることを保証します。
-
クライアントは、そのznodeに対応する新しいデータを確認する前に、監視しているznodeの監視イベントを確認します。
-
ZooKeeperからの監視イベントの順序は、ZooKeeperサービスによって認識される更新の順序に対応します。
ウォッチに関して覚えておくべきこと
-
標準の監視は1回限りのトリガーです。監視イベントを取得し、今後の変更を通知する場合は、別の監視を設定する必要があります。
-
標準の監視は1回限りのトリガーであり、イベントを取得してから監視を取得するための新しいリクエストを送信するまでに遅延があるため、ZooKeeperでノードに発生するすべての変更を確実に確認することはできません。イベントを取得してから監視を再度設定するまでの間にznodeが複数回変更される場合に備えてください。(気にならないかもしれませんが、少なくとも発生する可能性があることを理解してください。)
-
監視オブジェクト、または関数/コンテキストのペアは、特定の通知に対して1回だけトリガーされます。たとえば、同じ監視オブジェクトが同じファイルのexists呼び出しとgetData呼び出しに登録されていて、そのファイルが削除された場合、監視オブジェクトはファイルの削除通知で1回だけ呼び出されます。
-
サーバーから切断した場合(たとえば、サーバーが故障した場合)、接続が再確立されるまで監視は取得されません。このため、セッションイベントはすべての未処理の監視ハンドラーに送信されます。セッションイベントを使用してセーフモードに移行します。切断中はイベントを受信しないため、プロセスはそのモードで慎重に動作する必要があります。
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:子ノードを作成できます。
- READ:ノードからデータを取得し、その子をリストできます。
- WRITE:ノードのデータを設定できます。
- DELETE:子ノードを削除できます。
- ADMIN:パーミッションを設定できます。
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には、次の組み込みスキームがあります。
- worldには、誰でもを表す単一のID、anyone があります。
- auth は、提供された式をすべて無視し、代わりに現在のユーザー、資格情報、およびスキームを使用する特別なスキームです。提供された式(SASL認証のようなuser、またはDIGEST認証のような user:password など)は、ACLを永続化するときにZooKeeperサーバーによって無視されます。ただし、ACLは scheme:expression:perms の形式に一致する必要があるため、式はACLに依然として指定する必要があります。このスキームは、ユーザーがznodeを作成し、そのznodeへのアクセスをそのユーザーのみに制限するという一般的なユースケースであるため、便宜上提供されています。認証されたユーザーがいない場合、authスキームでACLを設定すると失敗します。
- digest は、username:password 文字列を使用してMD5ハッシュを生成し、それをACL IDとして使用します。認証は、username:password をクリアテキストで送信することによって行われます。ACLで使用する場合、式は、username:base64 エンコードされた SHA1 パスワードダイジェストになります。
- ip は、クライアントホストのIPをACL IDとして使用します。ACL式は、addr/bits の形式になります。ここで、addr の最上位の bits が、クライアントホストIPの最上位の bits と照合されます。
- x509 は、クライアントのX500プリンシパルをACL IDとして使用します。ACL式は、クライアントの正確なX500プリンシパル名です。セキュアポートを使用する場合、クライアントは自動的に認証され、x509スキームの認証情報が設定されます。
ZooKeeper C クライアント API
次の定数は、ZooKeeper Cライブラリによって提供されます。
- const int ZOO_PERM_READ; //ノードの値の読み取りとその子のリスト表示ができます
- const int ZOO_PERM_WRITE;// ノードの値を設定できます
- const int ZOO_PERM_CREATE; //子を作成できます
- const int ZOO_PERM_DELETE;// 子を削除できます
- const int ZOO_PERM_ADMIN; //set_acl()を実行できます
- const int ZOO_PERM_ALL;// 上記のすべてのフラグをORで組み合わせたもの
以下は、標準のACL IDです。
- struct Id ZOO_ANYONE_ID_UNSAFE; //(‘world’,’anyone’)
- struct Id ZOO_AUTH_IDS;// (‘auth’,’’)
ZOO_AUTH_IDSの空のID文字列は、「作成者のID」と解釈する必要があります。
ZooKeeperクライアントには、3つの標準ACLが付属しています。
- struct ACL_vector ZOO_OPEN_ACL_UNSAFE; //(ZOO_PERM_ALL,ZOO_ANYONE_ID_UNSAFE)
- struct ACL_vector ZOO_READ_ACL_UNSAFE;// (ZOO_PERM_READ, ZOO_ANYONE_ID_UNSAFE)
- struct ACL_vector ZOO_CREATOR_ALL_ACL; //(ZOO_PERM_ALL,ZOO_AUTH_IDS)
ZOO_OPEN_ACL_UNSAFEは、完全にオープンでフリーなすべての人向けのACLです。すべてのアプリケーションは、ノードで任意の操作を実行でき、その子を作成、リスト、および削除できます。ZOO_READ_ACL_UNSAFEは、すべてのアプリケーションの読み取り専用アクセスです。CREATE_ALL_ACLは、ノードの作成者にすべてのパーミッションを付与します。作成者は、このACLでノードを作成する前に、サーバーによって認証されている必要があります(たとえば、「digest」スキームを使用)。
次のZooKeeper操作は、ACLを扱います。
- int zoo_add_auth (zhandle_t *zh,const char* scheme,const char* cert, int certLen, void_completion_t completion, const void *data);
アプリケーションは、zoo_add_auth関数を使用してサーバーに認証を行います。アプリケーションが異なるスキームやIDを使用して認証する場合は、関数を複数回呼び出すことができます。
- int zoo_create (zhandle_t *zh, const char *path, const char *value,int valuelen, const struct ACL_vector *acl, int flags,char *realpath, int max_realpath_len);
zoo_create(...)操作は、新しいノードを作成します。aclパラメーターは、ノードに関連付けられたACLのリストです。親ノードには、CREATEパーミッションビットが設定されている必要があります。
- int zoo_get_acl (zhandle_t *zh, const char *path,struct ACL_vector *acl, struct Stat *stat);
この操作は、ノードのACL情報を返します。ノードには、READまたはADMINパーミッションが設定されている必要があります。ADMINパーミッションがない場合、ダイジェストハッシュ値はマスクアウトされます。
- int zoo_set_acl (zhandle_t *zh, const char *path, int version,const struct ACL_vector *acl);
この関数は、ノードの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が設定されます。認証プラグインは、独自のロジックとマッチングスキームを使用して、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を介して)を受け取ります。
- ZooKeeperServer ZooKeeperServerインスタンス
- ServerCnxn 現在の接続
- path 操作対象のZNodeパス(使用されていない場合はnull)
- perm 操作の値または0
- setAcls setAcl()メソッドが操作されている場合、設定されるACLのリスト
整合性の保証
ZooKeeperは、高性能でスケーラブルなサービスです。読み取り操作と書き込み操作の両方が高速になるように設計されていますが、読み取りの方が書き込みよりも高速です。これは、読み取りの場合、ZooKeeperが古いデータを提供できるためです。これは、ZooKeeperの整合性保証によるものです。
-
順次整合性:クライアントからの更新は、送信された順序で適用されます。
-
原子性:更新は成功するか失敗するかのどちらかです。部分的な結果はありません。
-
単一システムイメージ:クライアントは、接続するサーバーに関係なく、サービスに対して同じビューを表示します。つまり、クライアントは、同じセッションで別のサーバーにフェイルオーバーした場合でも、システムの古いビューを表示することはありません。
-
信頼性:更新が適用された場合、クライアントが更新を上書きするまで、その時点から永続化されます。この保証には、次の2つの帰結があります。
- クライアントが成功の戻りコードを受け取った場合、更新は適用されています。一部の障害(通信エラー、タイムアウトなど)では、クライアントは更新が適用されたかどうかを知ることができません。障害を最小限に抑えるための措置を講じますが、保証は成功の戻りコードでのみ存在します。(これはPaxosの単調性条件と呼ばれます。)
- クライアントが読み取りリクエストまたは正常な更新を通じて確認した更新は、サーバー障害から回復する際にロールバックされることはありません。
-
適時性:システムのクライアントのビューは、特定の時間範囲内(数十秒程度)で最新の状態であることが保証されています。システムの変更はこの範囲内にクライアントに認識されるか、クライアントがサービス停止を検出します。
これらの整合性保証を使用すると、リーダー選出、バリア、キュー、および読み取り/書き込み可能な取り消し可能ロックなどの高レベル関数を、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.zookeeperとorg.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スレッドで処理されます。非同期メソッドおよびウォッチイベントへのすべてのレスポンスは、イベントスレッドで処理されます。この設計から生じるいくつかの注意点があります。
- 非同期呼び出しとウォッチャーコールバックのすべての完了は、一度に1つずつ順番に行われます。呼び出し元は必要な処理を行うことができますが、その間は他のコールバックは処理されません。
- コールバックは、IOスレッドの処理や同期呼び出しの処理をブロックしません。
- 同期呼び出しは、正しい順序で返されない場合があります。たとえば、クライアントが次の処理を実行するとします。ノード/aの非同期読み取りをwatchをtrueに設定して発行し、読み取りの完了コールバックで/aの同期読み取りを実行します。(おそらく良い習慣ではありませんが、違法でもなく、簡単な例になります。)非同期読み取りと同期読み取りの間に/aへの変更があった場合、クライアントライブラリは同期読み取りへのレスポンスの前に/aが変更されたことを示すウォッチイベントを受信しますが、完了コールバックがイベントキューをブロックしているため、同期読み取りはウォッチイベントが処理される前に/aの新しい値を返します。
最後に、シャットダウンに関連するルールは簡単です。ZooKeeperオブジェクトが閉じられるか、致命的なイベント(SESSION_EXPIREDとAUTH_FAILED)を受信すると、ZooKeeperオブジェクトは無効になります。クローズすると、2つのスレッドがシャットダウンし、zookeeperハンドルへのそれ以降のアクセスは未定義の動作であり、回避する必要があります。
クライアント構成パラメータ
次のリストには、Javaクライアントの設定プロパティが含まれています。これらのプロパティは、Javaシステムプロパティを使用して設定できます。サーバープロパティについては、管理ガイドのサーバー構成セクションを確認してください。ZooKeeper Wikiには、ZooKeeper SSLサポートとZooKeeperのSASL認証に関する役立つページもあります。
-
zookeeper.sasl.client:SASL認証を無効にするには、値をfalseに設定します。デフォルトはtrueです。
-
zookeeper.sasl.clientconfig:JAASログインファイル内のコンテキストキーを指定します。デフォルトは「Client」です。
-
zookeeper.server.principal:Kerberos認証が有効になっている場合に、ZooKeeperサーバーへの接続時にクライアントが認証に使用するサーバープリンシパルを指定します。この構成が提供されている場合、ZooKeeperクライアントは、サーバープリンシパルを決定するために、次のパラメータのいずれも使用しません。 zookeeper.sasl.client.username、zookeeper.sasl.client.canonicalize.hostname、zookeeper.server.realm注:この構成パラメータは、ZooKeeper 3.5.7+、3.6.0+でのみ機能します。
-
zookeeper.sasl.client.username:従来、プリンシパルは、プライマリ、インスタンス、レルムの3つの部分に分割されます。一般的なKerberos V5プリンシパルの形式はprimary/instance@REALMです。 zookeeper.sasl.client.usernameは、サーバープリンシパルのプライマリ部分を指定します。デフォルトは「zookeeper」です。インスタンス部分は、サーバーIPから派生します。最後に、サーバーのプリンシパルはusername/IP@realmになります。ここで、usernameはzookeeper.sasl.client.usernameの値、IPはサーバーIP、realmはzookeeper.server.realmの値です。
-
zookeeper.sasl.client.canonicalize.hostname:zookeeper.server.principalパラメータが提供されていない場合、ZooKeeperクライアントはZooKeeperサーバープリンシパルの「インスタンス」(ホスト)部分を決定しようとします。最初に、ZooKeeperサーバー接続文字列として提供されたホスト名を取得します。次に、アドレスに属する完全修飾ドメイン名を取得することにより、アドレスを「正規化」しようとします。 zookeeper.sasl.client.canonicalize.hostname = falseを設定することにより、この「正規化」を無効にできます。
-
zookeeper.server.realm:サーバープリンシパルのレルム部分。デフォルトでは、クライアントプリンシパルレルムです。
-
zookeeper.disableAutoWatchReset:このスイッチは、自動ウォッチリセットが有効になっているかどうかを制御します。クライアントはデフォルトでセッション再接続中にウォッチを自動的にリセットします。このオプションを使用すると、zookeeper.disableAutoWatchResetをtrueに設定して、この動作をオフにできます。
-
zookeeper.client.secure : バージョン3.5.5で新規追加: サーバーのセキュアクライアントポートに接続する場合は、クライアント側でこのプロパティを true に設定する必要があります。これにより、指定されたクレデンシャルを使用してSSLでサーバーに接続します。Nettyクライアントが必要となることに注意してください。
-
zookeeper.clientCnxnSocket : 使用するClientCnxnSocketを指定します。可能な値は org.apache.zookeeper.ClientCnxnSocketNIO および org.apache.zookeeper.ClientCnxnSocketNetty です。デフォルトは org.apache.zookeeper.ClientCnxnSocketNIO です。サーバーのセキュアクライアントポートに接続する場合は、クライアント側でこのプロパティを org.apache.zookeeper.ClientCnxnSocketNetty に設定する必要があります。
-
zookeeper.ssl.keyStore.location and zookeeper.ssl.keyStore.password : バージョン3.5.5で新規追加: SSL接続に使用するローカルクレデンシャルを含むJKSファイルへのファイルパスと、ファイルをロック解除するためのパスワードを指定します。
-
zookeeper.ssl.keyStore.passwordPath : バージョン3.8.0で新規追加: キーストアのパスワードを含むファイルへのファイルパスを指定します。
-
zookeeper.ssl.trustStore.location and zookeeper.ssl.trustStore.password : バージョン3.5.5で新規追加: SSL接続に使用するリモートクレデンシャルを含むJKSファイルへのファイルパスと、ファイルをロック解除するためのパスワードを指定します。
-
zookeeper.ssl.trustStore.passwordPath : バージョン3.8.0で新規追加: トラストストアのパスワードを含むファイルへのファイルパスを指定します。
-
zookeeper.ssl.keyStore.type および zookeeper.ssl.trustStore.type: バージョン3.5.5で新規追加: ZooKeeperサーバーへのTLS接続を確立するために使用されるキー/トラストストアファイルのファイル形式を指定します。値:JKS、PEM、PKCS12、またはnull(ファイル名で検出)。デフォルト:null。バージョン3.6.3, 3.7.0で新規追加: BCFKS形式が追加されました。
-
jute.maxbuffer : クライアント側では、サーバーからの受信データの最大サイズを指定します。デフォルトは0xfffff(1048575)バイト、つまり1M弱です。これはあくまで健全性チェックです。ZooKeeperサーバーは、キロバイト単位のデータを保存および送信するように設計されています。受信データの長さがこの値を超える場合、IOExceptionが発生します。クライアント側のこの値は、サーバー側と同じにする必要があります(クライアント側でSystem.setProperty("jute.maxbuffer", "xxxx")を設定すると機能します)。そうしないと問題が発生します。
-
zookeeper.kinit : kinitバイナリへのパスを指定します。デフォルトは"/usr/bin/kinit"です。
C バインディング
Cバインディングには、シングルスレッドライブラリとマルチスレッドライブラリがあります。マルチスレッドライブラリは最も使いやすく、Java APIに最もよく似ています。このライブラリは、接続のメンテナンスとコールバックを処理するためのIOスレッドとイベントディスパッチスレッドを作成します。シングルスレッドライブラリでは、マルチスレッドライブラリで使用されているイベントループを公開することで、イベントドリブンアプリケーションでZooKeeperを使用できます。
パッケージには、zookeeper_stとzookeeper_mtの2つの共有ライブラリが含まれています。前者は、アプリケーションのイベントループに統合するための非同期APIとコールバックのみを提供します。このライブラリが存在する唯一の理由は、pthreadライブラリが利用できないか不安定なプラットフォーム(つまりFreeBSD 4.x)をサポートするためです。それ以外の場合は、すべてのアプリケーション開発者は、同期APIと非同期APIの両方のサポートが含まれているため、zookeeper_mtとリンクする必要があります。
インストール
Apacheリポジトリからチェックアウトしてクライアントをビルドする場合は、以下に示す手順に従ってください。Apacheからダウンロードしたプロジェクトソースパッケージからビルドする場合は、手順3にスキップしてください。
- zookeeper-juteディレクトリ(.../trunk/zookeeper-jute)で
mvn compile
を実行します。これにより、.../trunk/zookeeper-client/zookeeper-client-cの下に「generated」という名前のディレクトリが作成されます。 - ディレクトリを*.../trunk/zookeeper-client/zookeeper-client-c*に変更し、
autoreconf -if
を実行してautoconf、automake、およびlibtoolをブートストラップします。autoconfバージョン2.59以上がインストールされていることを確認してください。手順4にスキップします。 - プロジェクトソースパッケージからビルドする場合は、ソースtarballを解凍/展開し、*zookeeper-x.x.x/zookeeper-client/zookeeper-client-c*ディレクトリにcdします。
./configure <your-options>
を実行してmakefileを生成します。以下は、このステップで役立つ可能性のあるconfigureユーティリティがサポートするオプションの一部です。
--enable-debug
最適化を有効にし、デバッグ情報コンパイラオプションを有効にします。(デフォルトでは無効。)--without-syncapi
同期APIのサポートを無効にします。zookeeper_mtライブラリはビルドされません。(デフォルトでは有効。)--disable-static
スタティックライブラリをビルドしません。(デフォルトでは有効。)--disable-shared
共有ライブラリをビルドしません。(デフォルトでは有効。)
注記
configureの実行に関する一般的な情報については、INSTALLを参照してください。1.
make
またはmake install
を実行してライブラリをビルドしてインストールします。2. ZooKeeper APIのdoxygenドキュメントを生成するには、make doxygen-doc
を実行します。すべてのドキュメントは、docsという名前の新しいサブフォルダーに配置されます。デフォルトでは、このコマンドはHTMLのみを生成します。他のドキュメント形式については、./configure --help
を実行してください。
独自の C クライアントの構築
アプリケーションでZooKeeper C APIを使用できるようにするには、次のことを覚えておく必要があります。
- ZooKeeperヘッダーを含める:
#include <zookeeper/zookeeper.h>
- マルチスレッドクライアントをビルドする場合は、
-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);
-
*host : host:port形式のzookeeperサーバーへの接続文字列。複数のサーバーがある場合は、host:portペアを指定した後、コンマを区切り文字として使用します。例:"127.0.0.1:2181,127.0.0.1:3001,127.0.0.1:3002"
-
fn : 通知がトリガーされたときにイベントを処理するウォッチャー関数。
-
recv_timeout : セッションの有効期限(ミリ秒単位)。
-
*clientid : 新しいセッションには0を指定できます。セッションが以前に確立されている場合は、そのクライアントIDを指定すると、以前のセッションに再接続されます。
-
*context : zkhandle_tハンドラーに関連付けることができるコンテキストオブジェクト。使用しない場合は、0に設定できます。
-
flags : 初期化では、0のままにすることができます。
接続に成功した場合は「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ユーザーが陥る可能性のある落とし穴の一部です。
- ウォッチを使用している場合は、接続済みウォッチイベントを探す必要があります。ZooKeeperクライアントがサーバーから切断されると、再接続されるまで変更の通知は受信されません。znodeの出現を監視している場合は、切断中にznodeが作成および削除されると、イベントを見逃します。
- ZooKeeperサーバーの障害をテストする必要があります。ZooKeeperサービスは、サーバーの過半数がアクティブである限り、障害を生き残ることができます。問うべき質問は、アプリケーションがそれを処理できるかどうかです。現実の世界では、クライアントのZooKeeperへの接続が切断される可能性があります。(ZooKeeperサーバーの障害やネットワークパーティションは、接続が失われる一般的な理由です。)ZooKeeperクライアントライブラリは、接続の回復と発生した内容の通知を処理しますが、状態と失敗した未処理のリクエストを回復する必要があります。テストラボで正しく実行できたかどうかを確認し、実稼働環境では確認しないようにしてください。複数のサーバーで構成され、再起動の影響を受けるZooKeeperサービスでテストしてください。
- クライアントが使用するZooKeeperサーバーのリストは、各ZooKeeperサーバーが持つZooKeeperサーバーのリストと一致する必要があります。クライアントリストがZooKeeperサーバーの実際のリストのサブセットである場合は(最適ではありませんが)機能しますが、クライアントがZooKeeperクラスターにないZooKeeperサーバーをリストしている場合は機能しません。
- トランザクションログを配置する場所に注意してください。ZooKeeperの最もパフォーマンスが重要な部分は、トランザクションログです。ZooKeeperは、応答を返す前にトランザクションをメディアに同期する必要があります。専用のトランザクションログデバイスは、一貫した良好なパフォーマンスを実現するための鍵となります。ログをビジー状態のデバイスに配置すると、パフォーマンスに悪影響を及ぼします。ストレージデバイスが1つしかない場合は、トレースファイルをNFSに配置し、snapshotCountを増やしてください。問題は解消されませんが、緩和することができます。
- Javaの最大ヒープサイズを正しく設定してください。スワッピングを避けることが非常に重要です。不必要にディスクにアクセスすると、ほぼ確実にパフォーマンスが許容できないほど低下します。ZooKeeperでは、すべてが順序付けられているため、1つのリクエストがディスクにヒットすると、キューに入れられた他のすべてのリクエストがディスクにヒットすることを忘れないでください。スワッピングを回避するには、ヒープサイズを物理メモリの量から、OSとキャッシュに必要な量を引いた値に設定してみてください。構成に最適なヒープサイズを決定する最良の方法は、負荷テストを実行することです。何らかの理由でそれができない場合は、見積もりを控えめにして、マシンがスワップを引き起こす制限を十分に下回る数値を選択してください。たとえば、4Gマシンの場合、3Gヒープは最初に始めるための控えめな見積もりです。
その他の情報へのリンク
公式ドキュメント以外にも、ZooKeeperの開発者向けの有益な情報源がいくつかあります。
-
APIリファレンス : ZooKeeper APIの完全なリファレンス
-
Hadoop Summit 2008でのZooKeeperに関する講演 : Yahoo! Researchのベンジャミン・リードによるZooKeeperのビデオ紹介
-
バリアとキューのチュートリアル : Flavio Junqueiraによる優れたJavaチュートリアルで、ZooKeeperを使用してシンプルなバリアとプロデューサー・コンシューマーキューを実装します。
-
ZooKeeper - 信頼性の高いスケーラブルな分散協調システム : トッド・ホフによる記事(2008年7月15日)
-
ZooKeeperのレシピ : ZooKeeperを使用したさまざまな同期ソリューション(イベントハンドル、キュー、ロック、二相コミット)の実装に関する疑似レベルでの議論。