DUICUO

これらの概念を理解していない場合は、履歴書に Zookeeper に「精通している」と記載しないことをお勧めします。

[[321281]]

しつこい

この記事では主にZooKeeperの基本的な概念について解説します。本題に入る前に、私がこの業界に入った時の面接についてお話ししたいと思います。面接は率直で、少し愛着も湧くようなものでした。

インタビュアー: ZooKeeper を使用したことがありますか?

私:はい、使いました。ダボのサービスの登録と検索に使います。

インタビュアー: ZooKeeper とは何かご存知ですか?

私:わかっています。登録センターです。

インタビュアー: プロジェクトでは ZooKeeper をどのように使用していますか?

私: Spring Boot の application.properties 構成ファイルに ZooKeeper サービス アドレスを追加するだけです。

上記の会話は問題ないように思えますが、何かがおかしいと感じ、その結果、このようなインタビューに答えるたびに拒否されてしまいます。

なぜZooKeeperについて質問されたのでしょうか?履歴書にはZooKeeperに精通していると記載されていましたが、面接官にとっての「熟達」とは、単にZooKeeperの設定やプロジェクトがエラーなく開始されることを保証できる能力だけではありません。そのため、ZooKeeperに関する包括的な理解が依然として必要となります。

I. ZooKeeper について知るには?

HadoopプロジェクトのオープンソースサブプロジェクトであるZookeeperは、分散データ整合性ソリューションの典型です。分散アプリケーション向けに、高性能、高可用性、そして厳密に順序付けられたアクセス制御を備えた分散コーディネーションサービスを提供することに特化しています。

1. ZooKeeperデータモデル

ZooKeeper はファイルシステムのようなデータ構造を維持しており、各サブディレクトリ (/WeChat、/WeChat/Official Account) は znode と呼ばれます。ファイルシステムと同様に、znode は簡単に追加・削除でき、単一の znode の配下の子 znode も追加・削除できます。ファイルシステムとの違いは、znode はデータを保存できる点です (厳密に言えば、znode は必ずデータを保存する必要があります。デフォルトは空文字列です)。

ZooKeeper はディレクトリ ノード構造を使用するため、ノードを取得または作成するときにはノードが「/」で始まる必要があります。そうでない場合、ノードを取得するときに「パスは / 文字で始まる必要があります」というエラーが発生します。

  1. 1[zk: localhost:2181(CONNECTED) 13] テストを取得
  2. 2コマンドが失敗しました: java.lang.IllegalArgumentException: パスは/文字始まる必要があります 

ルートノード名は「/XXX」である必要があり、子ノードを作成するときは、ルートノードディレクトリ「/XXX/CCC」または「/XXX/AAA」を含める必要があります。

たとえば、下の画像の「Programmer Insider」ノードを取得するには、完全なパスを連結する必要があります: get /WeChat/Official Account/Programmer Insider

  1. 1get / WeChat / 公式アカウント / プログラマー向けヒント

znodeはバイトレベルまたはキロバイトレベルのデータを格納するために使用され、格納できるデータの最大量は1MBです(注:ノードのデータ量には、ノード自体に格納されているデータだけでなく、すべての子ノードの名前も含まれます。子ノードの名前はバイトに変換する必要があるため、znodeの子ノードの数は無制限ではありません)。ノードのストレージサイズは手動で変更できますが、通常は推奨されません。

2. znodeノード属性

znodeはデータを保存できるだけでなく、いくつかの特別な属性も持っています。次に、/testノードを作成し、その様々な属性の意味を分析します。

  1. 1[zk: localhost:2181(接続済み) 6] /test を取得
  2. 2456
  3. 3cZxid = 0x59ac //
  4. 4ctime = 2020年3月30日月曜日 15:20:08 CST
  5. 5mZxid = 0x59ad
  6. 6分時間 = 2020年3月30日月曜日 15:22:25 CST
  7. 7pZxid = 0x59ac
  8. 8cversion = 0
  9. 9データバージョン = 2
  10. 10aclバージョン = 0
  11. 11 一時所有者 = 0x0
  12. 12データ長 = 3
  13. 13numChildren = 0

znode には多くの属性があることがわかりますが、最も重要なのは zxid、version、acl です。

ジド:

znode の状態が変化すると、ノードは zxid 形式のタイムスタンプを受け取ります。このタイムスタンプはグローバルに順序付けられ、znode が作成または更新されるたびに新しいタイムスタンプが生成されます。zxid1 の値が zxid2 の値より小さい場合、zxid2 の変更は zxid1 より後に発生したことになります。各 znode には、cZxid(ノード作成時刻)、mZxid(ノード変更時刻、子ノードとは無関係)、pZxid(このノードまたはその子ノードの最終作成時刻または最終変更時刻、孫ノードとは無関係)の 3 つの zxid 属性があります。

zxid 属性は主に ZooKeeper クラスターで使用されます。これについては、後ほどクラスターについて説明するときに詳しく説明します。

バージョン:

znode プロパティには、dataversion (データ バージョン番号)、cversion (子ノード バージョン番号)、aclversion (ノードの ACL 権限バージョン番号) の 3 つのバージョン番号があります。

znode 内のデータは複数のバージョンを持つことができます。ノードに複数のバージョンのデータが含まれている場合、そのノードのデータをクエリするにはバージョン番号を含める必要があります。znode 内のデータを変更するたびに、ノードの `dataVersion` 番号が増加します。クライアントが znode を要求すると、ノードデータとバージョン番号の両方が返されます。また、`dataVersion` が -1 の場合、操作中にバージョンは無視されます。ノードの権限を設定すると、`aclVersion` 番号が増加します。ACL 権限制御については後述します。

これを検証するために、/testノードのデータに変更を加え、dataVersionの変化を観察しました。dataVersion属性が3に変更され、バージョン番号が増加したことがわかりました。

  1. 1[zk: localhost:2181(接続済み) 10] /test 8888を設定
  2. 2cZxid = 0x59ac
  3. 3ctime = 2020 年 3 月 30 日月曜日 15:20:08 CST
  4. 4mZxid = 0x59b6
  5. 5mtime = 2020年3月30日月曜日 16:58:08 CST
  6. 6pZxid = 0x59ac
  7. 7cversion = 0
  8. 8データバージョン = 3
  9. 9aclバージョン = 0
  10. 10ephemeralOwner = 0x0
  11. 11データ長 = 4
  12. 12numChildren = 0

3. znodeの種類

ZooKeeper には 4 種類の znode があり、クライアントを使用してノードを作成するときにタイプを指定する必要があります。

  1. 1. zookeeper.create ( "/public account/ programmer insider" , "" .getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
  • PERSISTENT - 永続ディレクトリノード: クライアントがノードを作成し、ZooKeeper から切断した後も、ノードは永続化され、クライアントが再接続しても存在し続けます。
  • PERSISTENT_SEQUENTIAL - 永続的シーケンシャルノード:クライアントがノードを作成し、ZooKeeperから切断した後も、ノードは永続化されます。クライアントが再接続しても、ノードは依然として存在します。ZooKeeperはノード名にシーケンシャル番号を割り当てます(例:/lock/0000000001、/lock/0000000002、/lock/0000000003)。
  • EPHEMERAL - 一時ディレクトリ ノード: このノードは、クライアントが ZooKeeper から切断された後に削除されます。
  • EPHEMERAL_SEQUENTIAL - 一時シーケンシャルノード: このノードは、クライアントがZooKeeperから切断されると削除されます。ノード名には、/lock/0000000001、/lock/0000000002、/lock/0000000003のようなシーケンシャル番号が割り当てられます。

II. ノードのACL権限制御

ACLはアクセス制御リスト(Access Control List)の略です。これは、znodeノードのアクセス制御問題を解決するために使用されます。ZooKeeperのアクセス制御はznodeレベルに基づいているため、ノード間の権限は継承されないことに注意してください。つまり、子ノードは親ノードの権限を継承しません。

ZooKeeper で ACL 権限を設定する形式は次のとおりです。 :それは3つの部分から構成されます。

スキーマ: 認証方法を表す

  • world: 誰でもアクセスできることを示します。
  • 認証: 認証されたユーザーのみがアクセスできます。
  • ダイジェスト: ユーザー名とパスワードを認証 ID として使用して MD5 ハッシュ値を生成します。
  • host/ip: 認証にクライアントのホスト IP アドレスを使用します。

id: 選択したスキーマに応じて異なる、ID を識別するために使用される権限の範囲。

acl: ノードに割り当てる権限を指定します。ノード権限には、作成、削除、書き込み、読み取り、管理者権限があり、これらを総称してcdwraと呼びます。

1. ワールド: 誰でもアクセスできることを示します。

getAcl コマンドを使用して、権限が設定されていない znode ノードのデフォルトの権限を確認してみましょう。

  1. 1[zk: localhost:2181(接続済み) 12] getAcl /test
  2. 2 「世界」、誰でも
  3. 3: cdrwa

ACL属性のないノードが表示されています。デフォルトのスキーマは「world」、スコープは「anyone」、ノード権限は「cdwra」で、誰でもアクセスできることを意味します。

スキーマがワールドではないノードにワールド権限を設定するにはどうすればよいですか?

  1. 1setAcl /test world:anyone:crdwa

2. 認証: 認証されたユーザーのみがアクセスできます。

スキーマは認証認可を使用して、認証されたユーザーのみがアクセスできるようにします。そのため、最初のステップは認証されたユーザーを追加し、それらのユーザーに対してACL権限を設定することです。

  1. 1addauthダイジェストテスト:パスワード(プレーンテキスト)

認証されたユーザーのパスワードはプレーンテキストであることに注意してください。

  1. 1 [zk: localhost:2181(CONNECTED) 2] addauth digest user : user //username:password
  2. 2[zk: localhost:2181(接続済み) 5] setAcl /test auth:ユーザー:crdwa
  3. 3[zk: localhost:2181(接続済み) 6] getAcl /test
  4. 4 'ダイジェスト'、ユーザー:ben+k/3JomjGj4mfd4fYsfM6p0A=
  5. 5: cdrwa

実際、このように設定することで、このノードをすべての認証済みユーザーに公開します。`setAcl /test auth:user:crdwa` は `setAcl /test auth::crdwa` と同等です。

3. ダイジェスト: ユーザー名: パスワード認証方法

ユーザー名:パスワード認証は特定の単一のユーザーを対象としており、この方法では最初に認証されたユーザーを追加する必要はありません。

ZooKeeper クライアントを使用してコード内で ACL を設定する場合、パスワードは平文で保存されます。ただし、zk.cli などのクライアントを使用する場合は、SHA1 と base64 を使用してパスワードを処理する必要があります。

  1. 1setAcl <パス> ダイジェスト:<ユーザー> : <パスワード(暗号文)>:<acl>
  2. 2
  3. 3setAcl /テストダイジェスト:ユーザー:jalRr+knv/6L2uXdenC93dEDNuE=:crdwa

では、パスワードを暗号化するにはどうすればいいのでしょうか?いくつかの方法があります。

シェルコマンドによる暗号化

  1. 1echo -n <ユーザー> : <パスワード> | openssl dgst -バイナリ-sha1 | openssl base64
  2. 2

組み込みの ZooKeeper ライブラリ org.apache.zookeeper.server.auth.DigestAuthenticationProvider を使用して生成します。

  1. 1java -cp /zookeeper-3.4.13/zookeeper-3.4.13.jar:/zookeeper-3.4.13/lib/slf4j-api-1.7.25.jar \
  2. 2 org.apache.zookeeper.server.auth.DigestAuthenticationProvider \
  3. 3 ルート:ルート
  4. 4root:root->root:qiTlqPLK7XM2ht3HMn02qRpkKIE=

4. host/ip: 認証にクライアントのホスト IP アドレスを使用します。

この方法は理解しやすく、特定の IP アドレス、または IP アドレスの範囲を承認します。

  1. 1[zk: localhost:2181(接続済み) 3] setAcl /test0000000014 ip:127.0.0.1:crdwa
  2. 2cZxid = 0x59ac
  3. 3ctime = 2020 年 3 月 30 日月曜日 15:20:08 CST
  4. 4mZxid = 0x59b6
  5. 5mtime = 2020年3月30日月曜日 16:58:08 CST
  6. 6pZxid = 0x59ac
  7. 7cversion = 0
  8. 8データバージョン = 3
  9. 9aclVersion = 3 // このバージョンは常に更新されています。
  10. 10ephemeralOwner = 0x0
  11. 11データ長 = 4
  12. 12numChildren = 0

III. ZooKeeperの魂:ウォッチャー

冒頭で述べたように、ZooKeeperはレジストリセンターとしてDubboにサービス登録と検出機能を提供します。しかし、ZooKeeperがなぜサービス登録と検出を実現できるのか疑問に思ったことはありませんか?そこで、ZooKeeperの中核機能であるWatcherについて説明します。

1. ウォッチャーとは何ですか?

ウォッチャーはZooKeeperの中核機能です。クライアント側のウォッチャーは、ノードとその子ノードのデータの変化を監視できます。これらの状態が変化すると、ZooKeeperサーバーはそのノードにウォッチャーを設定したすべてのクライアントに通知します。これにより、各クライアントは監視対象のノードの状態変化を迅速に検知し、対応する論理アクションを実行できます。

ウォッチャーについて簡単に紹介したので、ZooKeeper がサービス登録と検出をどのように実装するかを分析しましょう。

ZooKeeper のサービス登録と検出は、主に ZooKeeper の znode データモデルとウォッチャーメカニズムを利用します。一般的なプロセスは次のとおりです。

  • サービス登録: サービスプロバイダーは起動時に、ZooKeeperサーバーにサービス情報を登録します。これはノードの作成を意味します。例えば、ユーザーはcom.xxx.user.registerというサービスを登録し、関連データ(サービスプロバイダーのIPアドレス、ポートなど)をノードに保存します。
  • サービスディスカバリ: サービスコンシューマーは起動時に、設定された依存サービス情報に基づいてZooKeeperサーバーから登録済みのサービス情報を取得し、監視機能を設定します。登録済みのサービス情報を取得した後、サービスプロバイダー情報をローカルにキャッシュし、サービスを呼び出します。
  • サービス通知: サービスプロバイダが何らかの理由でダウンし、サービスの提供を停止すると、クライアントはZooKeeperサーバーとの接続を失い、ZooKeeperサーバー上の当該サービスプロバイダに対応するサービスノード(例:ユーザー登録サービス com.xxx.user.register)が削除されます。その後、ZooKeeperサーバーは、サービス com.xxx.user.register を登録し、ノードの削除をリッスンする監視を設定しているすべてのコンシューマーユーザーに非同期的に通知を送信します。コンシューマーは受信した通知に基づいて最新のサービスリストを取得し、ローカルキャッシュ内のサービスリストを更新します。

上記のプロセスは、ZooKeeper がサービスを登録および検出する機能の背後にある一般的な原則です。

2. ウォッチャー型

Znodeには、DataWatchとDataWatchesという2種類のウォッチを設定できます。DataWatchは、znode上のデータの変更に基づいてトリガーされます。トリガー条件は、getData()、exists()、setData()、create()です。

もう1つのタイプは、子ノードウォッチです。これは、znodeの子ノードが変更されたときにトリガーされるウォッチイベントです。トリガー条件はgetChildren()とcreate()です。

delete() メソッドが呼び出されてznodeが削除されると、データウォッチと子ウォッチの両方が同時にトリガーされます。削除されたノードに親ノードがある場合、親ノードが子ウォッチをトリガーします。

3. ウォッチャーの特徴

ノードの監視機能は1回限りのイベントです。クライアントが特定のノードに監視機能を設定し、そのノードのデータの変更をクライアントに通知すると、そのノードに対するクライアントの監視機能は無効になります。

このノードの監視を継続したい場合は、クライアントのリスナーコールバックでノードの監視イベントを再度Trueに設定する必要があります。そうしないと、クライアントはノードの変更通知を一度しか受信しません。

IV. ZooKeeper はどのような機能を実行できますか?

サービス登録と検出機能はZooKeeperの氷山の一角に過ぎません。分散ロック、キュー、構成センターといった一連の機能も実装できます。ここでは原理のみを分析します。具体的な実装については、オンラインで詳細情報をご覧ください。

1. 分散ロック

ZooKeeper は、ウォッチャー機構と znode の順序付きノードに基づいており、本質的に分散ロックとして機能する可能性があります。まず、親ノード `/test/lock` がロックとして作成されます。このノードは永続ノード(PERSISTENT 型)が望ましいです。このロックを取得しようとする各クライアントは、親ノード `/test/lock` の下に、一時的なシーケンシャルな子ノードを作成します。

シーケンス番号は増分的に増加するため、最も小さいシーケンス番号を持つノードがロックを取得するように規定されています。例えば、クライアントがロックを取得すると、/test/lock ノードの下に /test/lock/seq-00000001 というノードが作成されます。このノードはシーケンス番号が最も小さいため、最初にロックを取得し、他のノードはロック取得の通知を待機します。/test/lock/seq-00000001 の処理が完了すると、ノードが削除され、ロックが解放されます。

では、ノード /test/lock/seq-00000002 は誰から通知を受けてロックを取得する必要があるのでしょうか?

ここでは、/test/lock/seq-00000002 ノードが /test/lock/seq-00000001 ノードをリッスンしています。/test/lock/seq-00000001 ノードが削除されると、/test/lock/seq-00000002 ノードに通知が送られ、自身が最小ノードかどうかを再度確認します。最小ノードであればロックを取得でき、そうでない場合は通知を待機し続けます。

同様に、ノード /test/lock/seq-00000003 はノード /test/lock/seq-00000002 をリッスンします。常に次のノードが前のノードをリッスンするようにし、すべてのノードが最小のノードをリッスンしないようにします。これにより、不要なリッスンを防ぎ、大量の無効な通知が生成されて「herd effect(群衆効果)」が生じるのを防ぎます。

Redis 分散ロックと比較すると、ZooKeeper 分散ロックは、多数のノードの作成および削除操作を処理する際のパフォーマンスが低いため、あまりお勧めできません。

2. 分散キュー

ZooKeeper での分散キューの実装も非常にシンプルです。znode の自然な「先入先出」(FIFO)原理を活用することで、最後に作成されたノードが常に最大になり、シーケンス番号が最も小さいノードが常にキューから削除されます。

3. 構成管理

現在、多くのオープンソースプロジェクトが設定の維持にZooKeeperを使用しています。例えば、メッセージキューKafkaはブローカー情報の維持にZooKeeperを使用し、Dubboはサービス設定情報を管理しています。この仕組みはウォッチャーメカニズムに基づいています。例えば、いくつかの設定を保存するために「/config」ノードが作成されます。クライアントはこのノードをリッスンし、「/config」ノードの設定情報が変更されると、すべてのクライアントにデータの変更が通知され、新しい設定情報を取得するように促されます。

4. ネーミングサービス

ZooKeeper のネーミングサービス(サービス登録・検出とも呼ばれます)は、主に指定された名前に基づいて、リソースまたはサービスのアドレスやサービスプロバイダなどの情報を取得します。znode ノードの特性とウォッチャー機構を活用することで、サービス情報を動的に登録・取得するための設定センターとして機能し、サービス名とそれに対応するサーバーリスト情報を一元管理します。これにより、バックエンドサーバーの状態(オンライン、オフライン、ダウンタイム)をほぼリアルタイムで把握できます。

要約

この記事は、ZooKeeper の基礎を紹介することを目的としています。ZooKeeper のクラスタリーダー選出など、面接でよく聞かれる概念については、クラスタリングは複雑なトピックであり、長文の記事は読者にとって負担が大きすぎるのではないかと懸念したため、ここでは取り上げません(実は、単に怠けていただけなのですが、笑)。

ご興味がありましたら、ぜひフォローしてください。次回はZooKeeperクラスターの動画でお会いしましょう!

今日はこれで終わりです。この記事が少しでもお役に立てたら、ぜひ「いいね👍」をお願いします!

あなたの評価が私の執筆のモチベーションです!