ZooKeeper 動的再構成
概要
3.5.0リリース以前は、ZooKeeperのメンバーシップと他のすべてのパラメータは静的で、起動時にロードされ、実行時には変更できませんでした。運用担当者は、設定を変更するために「ローリング再起動」に頼っていました。これは、手作業が多く、エラーが発生しやすい方法であり、本番環境でデータの損失と不整合を引き起こしていました。
3.5.0以降、「ローリング再起動」は不要になりました! ZooKeeperは、自動化された設定変更を完全にサポートしています。ZooKeeperサーバーのセット、それらのロール(参加者/オブザーバー)、すべてのポート、さらにはクォーラムシステムでさえ、サービスを中断することなく、データの整合性を維持しながら、動的に変更できます。再構成は、ZooKeeperの他の操作と同様に、すぐに実行されます。1つの再構成コマンドを使用して、複数の変更を行うことができます。動的再構成機能は、操作の同時実行性を制限せず、再構成中にクライアント操作を停止する必要がなく、管理者にとって非常にシンプルなインターフェースを備えており、他のクライアント操作に複雑さを加えることはありません。
新しいクライアント側の機能により、クライアントは設定変更を検出し、ZooKeeperハンドルに格納されている接続文字列(サーバーとそのクライアントポートのリスト)を更新できます。確率的アルゴリズムを使用して、新しい構成サーバー間でクライアントのバランスを取りながら、クライアントの移行の範囲をアンサンブルメンバーシップの変更に比例させます。
このドキュメントでは、再構成の管理者マニュアルを提供します。再構成アルゴリズム、パフォーマンス測定などの詳細については、次の論文を参照してください。
- Shraer, A., Reed, B., Malkhi, D., Junqueira, F. Dynamic Reconfiguration of Primary/Backup Clusters. In USENIX Annual Technical Conference (ATC)(2012), 425-437 : リンク: 論文 (pdf), スライド (pdf), ビデオ, Hadoop Summit スライド
注: 3.5.3以降、動的再構成機能はデフォルトで無効になっており、reconfigEnabled 設定オプションを介して明示的に有効にする必要があります。
設定フォーマットの変更
クライアントポートの指定
サーバーのクライアントポートは、サーバーがクライアント接続要求を受け入れるポートです。3.5.0以降、 clientPort および clientPortAddress 設定パラメータは使用しないでください。代わりに、この情報はサーバーキーワードの指定の一部になり、次のようになります。
server.<positive id> = <address1>:<port1>:<port2>[:role];[<client port address>:]<client port>**
クライアントポートの指定はセミコロンの右側にあります。クライアントポートアドレスはオプションであり、指定しない場合はデフォルトで「0.0.0.0」になります。通常どおり、ロールもオプションであり、 participant または observer (デフォルトでは participant )にすることができます。
有効なサーバーステートメントの例
server.5 = 125.23.63.23:1234:1235;1236
server.5 = 125.23.63.23:1234:1235:participant;1236
server.5 = 125.23.63.23:1234:1235:observer;1236
server.5 = 125.23.63.23:1234:1235;125.23.63.24:1236
server.5 = 125.23.63.23:1234:1235:participant;125.23.63.23:1236
複数のサーバーアドレスの指定
ZooKeeper 3.6.0以降、各ZooKeeperサーバーに複数のアドレスを指定することが可能になりました(ZOOKEEPER-3188 を参照)。これにより、可用性が向上し、ZooKeeperにネットワークレベルの回復力が追加されます。サーバーに複数の物理ネットワークインターフェースが使用されている場合、ZooKeeperはすべてのインターフェースにバインドでき、ネットワークエラーが発生した場合に動作しているインターフェースにランタイムで切り替えることができます。異なるアドレスは、パイプ( '|')文字を使用して設定で指定できます。
複数のアドレスを使用した有効な構成の例
server.2=zoo2-net1:2888:3888|zoo2-net2:2889:3889;2188
server.2=zoo2-net1:2888:3888|zoo2-net2:2889:3889|zoo2-net3:2890:3890;2188
server.2=zoo2-net1:2888:3888|zoo2-net2:2889:3889;zoo2-net1:2188
server.2=zoo2-net1:2888:3888:observer|zoo2-net2:2889:3889:observer;2188
standaloneEnabled フラグ
3.5.0より前は、ZooKeeperをスタンドアロンモードまたは分散モードで実行できました。これらは別々の実装スタックであり、実行時に切り替えることはできません。デフォルトでは(後方互換性のために) standaloneEnabled は true に設定されています。このデフォルトを使用した場合の結果として、単一のサーバーで起動した場合、アンサンブルは増加できず、複数のサーバーで起動した場合、2つ未満の参加者を含むように縮小することはできません。
フラグを false に設定すると、アンサンブルに単一の参加者しかいない場合でも、分散ソフトウェアスタックを実行するようにシステムに指示します。これを達成するには、(静的)設定ファイルに次が含まれている必要があります
standaloneEnabled=false**
この設定では、単一の参加者を含むZooKeeperアンサンブルを開始し、さらにサーバーを追加することで動的に拡張できます。同様に、サーバーを削除することにより、単一の参加者だけが残るようにアンサンブルを縮小することもできます。
分散モードを実行すると柔軟性が高まるため、フラグを false に設定することをお勧めします。レガシーのスタンドアロンモードは将来廃止される予定です。
reconfigEnabled フラグ
3.5.0以降3.5.3より前は、動的再構成機能を無効にする方法はありませんでした。再構成が有効になっていると、悪意のある攻撃者がZooKeeperアンサンブルの構成を任意に変更できるというセキュリティ上の懸念があるため、再構成機能を無効にするオプションを提供したいと思います。たとえば、侵害されたサーバーをアンサンブルに追加したり、正当なサーバーを削除したりできます。このようなケースは、ケースバイケースでセキュリティの脆弱性となる可能性があります。そのため、3.5.3では、reconfigEnabled 設定オプションが導入され、再構成機能を完全に無効にすることができ、認証の有無にかかわらずreconfig APIを介してクラスターを再構成しようとすると、reconfigEnabled が true に設定されていない限り、デフォルトで失敗します。.
オプションをtrueに設定するには、設定ファイル(zoo.cfg)に次が含まれている必要があります
reconfigEnabled=true
動的設定ファイル
3.5.0以降、実行時に変更できる動的設定パラメータと、サーバーの起動時に設定ファイルから読み取られ、実行中には変更されない静的設定パラメータを区別しています。現在、次の設定キーワードは動的設定の一部と見なされます: server、 group、 weight。
動的設定パラメータは、サーバー上の個別のファイル(動的設定ファイルと呼びます)に格納されます。このファイルは、新しい dynamicConfigFile キーワードを使用して静的設定ファイルからリンクされます。
例
zoo_replicated1.cfg
tickTime=2000
dataDir=/zookeeper/data/zookeeper1
initLimit=5
syncLimit=2
dynamicConfigFile=/zookeeper/conf/zoo_replicated1.cfg.dynamic
zoo_replicated1.cfg.dynamic
server.1=125.23.63.23:2780:2783:participant;2791
server.2=125.23.63.24:2781:2784:participant;2792
server.3=125.23.63.25:2782:2785:participant;2793
アンサンブル構成が変更されても、静的構成パラメータは変わりません。動的パラメータはZooKeeperによってプッシュされ、すべてのサーバー上の動的構成ファイルを上書きします。したがって、異なるサーバー上の動的構成ファイルは通常同一です(再構成が進行中である場合、または新しい構成が一部のサーバーにまだ伝播されていない場合にのみ、一時的に異なる可能性があります)。作成された動的設定ファイルは、手動で変更しないでください。変更は、以下に概説する新しい再構成コマンドによってのみ行われます。オフラインクラスターの設定を変更すると、ZooKeeperログ(およびログから入力された特別な設定znode)に格納されている設定情報に関して不整合が発生する可能性があるため、強くお勧めしません。
例2
ユーザーは最初に単一の構成ファイルを指定することを好む場合があります。したがって、以下も有効です
zoo_replicated1.cfg
tickTime=2000
dataDir=/zookeeper/data/zookeeper1
initLimit=5
syncLimit=2
clientPort=
各サーバーの設定ファイルは、まだこの形式になっていない場合は、自動的に動的ファイルと静的ファイルに分割されます。したがって、上記の構成ファイルは、例1の2つのファイルに自動的に変換されます。このプロセス中にclientPortとclientPortAddressの行(指定されている場合)は、冗長な場合は自動的に削除されます(上記の例のように)。元の静的構成ファイルはバックアップされます(.bakファイルに)。
後方互換性
古い設定フォーマットも引き続きサポートしています。たとえば、次の設定ファイルは許容されます(ただし推奨されません)
zoo_replicated1.cfg
tickTime=2000
dataDir=/zookeeper/data/zookeeper1
initLimit=5
syncLimit=2
clientPort=2791
server.1=125.23.63.23:2780:2783:participant
server.2=125.23.63.24:2781:2784:participant
server.3=125.23.63.25:2782:2785:participant
起動時に、動的設定ファイルが作成され、前述のように設定の動的部分が含まれます。ただし、この場合、「clientPort = 2791」行は、設定フォーマットの変更セクションで説明されているフォーマットを使用して「server.1 = ...」の一部として指定されていないため、サーバー1の静的設定ファイルに残ります。サーバー1のクライアントポートを設定する再構成が呼び出された場合、静的設定ファイルから「clientPort = 2791」を削除します(動的ファイルには、サーバー1の指定の一部としてこの情報が含まれるようになりました)。
3.5.0へのアップグレード
実行中のZooKeeperアンサンブルを3.5.0にアップグレードするには、アンサンブルを3.4.6リリースにアップグレードした後にのみ実行する必要があります。これは、ローリングアップグレードにのみ必要です(システムを完全にシャットダウンしても問題ない場合は、3.4.6を実行する必要はありません)。3.4.6を経由せずにローリングアップグレードを試行すると(たとえば、3.4.5から)、次のエラーが発生する可能性があります
2013-01-30 11:32:10,663 [myid:2] - INFO [localhost/127.0.0.1:2784:QuorumCnxManager$Listener@498] - Received connection request /127.0.0.1:60876
2013-01-30 11:32:10,663 [myid:2] - WARN [localhost/127.0.0.1:2784:QuorumCnxManager@349] - Invalid server id: -65536
ローリングアップグレード中は、各サーバーが順番に停止され、新しい3.5.0バイナリで再起動されます。3.5.0バイナリでサーバーを起動する前に、すべてのサーバーステートメント「server.x = ...」にクライアントポートが含まれるように設定ファイルを更新することを強くお勧めします(クライアントポートの指定セクションを参照)。前述のように、設定を単一のファイルに残したり、clientPort / clientPortAddressステートメントを残したりすることもできます(ただし、新しい形式でクライアントポートを指定すると、これらのステートメントは冗長になります)。
ZooKeeperアンサンブルの動的再構成
ZooKeeper JavaおよびC APIは、再構成を容易にするgetConfigおよびreconfigコマンドで拡張されました。どちらのコマンドにも、同期(ブロッキング)バリアントと非同期バリアントがあります。ここでは、Java CLIを使用してこれらのコマンドを示しますが、C CLIを同様に使用したり、他のZooKeeperコマンドと同様にプログラムから直接コマンドを呼び出したりできることに注意してください。
API
JavaとCの両方のクライアントに2セットのAPIがあります。
-
再構成API:再構成APIは、ZooKeeperクラスターを再構成するために使用されます。3.5.3以降、再構成Java APIはZooKeeperクラスからZooKeeperAdminクラスに移動され、このAPIを使用するにはACLのセットアップとユーザー認証が必要です(詳細については、セキュリティを参照してください)。
-
構成の取得API:構成の取得APIは、/ zookeeper / config znodeに格納されているZooKeeperクラスター構成情報を取得するために使用されます。/ zookeeper / configはどのユーザーでも読み取り可能であるため、このAPIを使用するために特別な設定や認証は必要ありません。
セキュリティ
3.5.3より前は、再構成に対するセキュリティメカニズムが適用されていなかったため、ZooKeeperサーバーアンサンブルに接続できるZooKeeperクライアントはすべて、再構成を介してZooKeeperクラスターの状態を変更できました。したがって、悪意のあるクライアントが侵害されたサーバーをアンサンブルに追加する可能性があります。たとえば、侵害されたサーバーを追加したり、正当なサーバーを削除したりします。このようなケースは、ケースバイケースでセキュリティの脆弱性となる可能性があります。
このセキュリティ問題に対処するため、バージョン **3.5.3** 以降、再構成に対するアクセス制御を導入しました。これにより、特定のユーザーセットのみが再構成コマンドまたはAPIを使用できるようになり、これらのユーザーは明示的に設定する必要があります。さらに、ZooKeeperクラスタのセットアップでは、ZooKeeperクライアントを認証できるように認証を有効にする必要があります。
また、安全な環境(企業のファイアウォールの内側など)でZooKeeperアンサンブルを操作および対話するユーザーのために、エスケープハッチも提供しています。再構成機能を使用したいが、再構成アクセスチェックのために承認ユーザーの明示的なリストを設定するオーバーヘッドを避けたいユーザーは、"skipACL" を "yes" に設定することで、ACLチェックをスキップし、すべてのユーザーがクラスタを再構成できるようにすることができます。
全体的に、ZooKeeperは再構成機能に対して柔軟な設定オプションを提供し、ユーザーはセキュリティ要件に基づいて選択できます。適切なセキュリティ対策が講じられているかどうかを判断するのは、ユーザーの裁量に任されています。
-
アクセス制御 : 動的な構成は、特別なznode ZooDefs.CONFIG_NODE = /zookeeper/config に格納されます。このノードはデフォルトで、スーパーユーザーと書き込みアクセスが明示的に設定されたユーザーを除くすべてのユーザーに対して読み取り専用です。再構成コマンドまたは再構成APIを使用する必要があるクライアントは、CONFIG_NODEへの書き込みアクセス権を持つユーザーとして設定する必要があります。デフォルトでは、スーパーユーザーのみがCONFIG_NODEへの書き込みアクセスを含む完全な制御権を持っています。追加のユーザーには、指定されたユーザーに書き込み権限を関連付けたACLを設定することにより、スーパーユーザーを介して書き込みアクセス権を付与できます。ACLを設定し、認証を使用して再構成APIを使用する方法のいくつかの例は、ReconfigExceptionTest.javaとTestReconfigServer.ccにあります。
-
認証 : ユーザーの認証はアクセス制御とは直交しており、ZooKeeperのプラグ可能な認証スキームでサポートされている既存の認証メカニズムに委任されます。このトピックの詳細については、ZooKeeper and SASL を参照してください。
-
ACLチェックの無効化 : ZooKeeperは、skipACLが "yes" に設定されている場合、ACLチェックが完全にスキップされるように、"skipACL" オプションをサポートしています。このような場合、認証されていないユーザーでも再構成APIを使用できます。
現在の動的設定の取得
動的構成は、特別なznode ZooDefs.CONFIG_NODE = /zookeeper/config に格納されます。新しい config
CLIコマンドはこのznodeを読み取ります(現在は単に get /zookeeper/config
のラッパーです)。通常の読み取りと同様に、最後にコミットされた値を取得するには、最初に sync
を実行する必要があります。
[zk: 127.0.0.1:2791(CONNECTED) 3] config
server.1=localhost:2780:2783:participant;localhost:2791
server.2=localhost:2781:2784:participant;localhost:2792
server.3=localhost:2782:2785:participant;localhost:2793
出力の最後の行に注目してください。これは構成バージョンです。バージョンは、この構成を作成した再構成コマンドのzxidと等しくなります。最初に確立された構成のバージョンは、最初に正常に確立されたリーダーによって送信されたNEWLEADERメッセージのzxidと等しくなります。構成が動的構成ファイルに書き込まれると、バージョンは自動的にファイル名の一部になり、静的構成ファイルは新しい動的構成ファイルへのパスで更新されます。以前のバージョンの構成ファイルは、バックアップのために保持されます。
起動時に、バージョン(存在する場合)はファイル名から抽出されます。バージョンは、ユーザーまたはシステム管理者によって手動で変更されるべきではありません。システムは、どの構成が最新であるかを知るためにこれを使用します。手動で操作すると、データの損失や不整合が発生する可能性があります。
get
コマンドと同様に、config
CLIコマンドは、znodeにウォッチを設定するための -w フラグと、znodeの統計情報を表示するための -s フラグを受け入れます。さらに、現在の構成に対応するバージョンとクライアント接続文字列のみを出力する新しいフラグ -c を受け入れます。たとえば、上記の構成では、次のようになります。
[zk: 127.0.0.1:2791(CONNECTED) 17] config -c
400000003 localhost:2791,localhost:2793,localhost:2792
APIを直接使用する場合、このコマンドは getConfig
と呼ばれることに注意してください。
読み取りコマンドと同様に、クライアントが接続されているフォロワーに認識されている構成が返されます。これは、わずかに古い可能性があります。より強力な保証のために、sync
コマンドを使用できます。たとえば、Java APIを使用すると、次のようになります。
zk.sync(ZooDefs.CONFIG_NODE, void_callback, context);
zk.getConfig(watcher, callback, context);
注:3.5.0では、すべてのサーバーの状態がリーダーと最新の状態になるため、sync()
コマンドにどのパスが渡されるかは実際には問題ありません(そのため、ZooDefs.CONFIG_NODEの代わりに別のパスを使用できます)。ただし、これは将来変更される可能性があります。
現在の動的設定の変更
構成の変更は、reconfig
コマンドを使用して行われます。再構成には、増分と非増分(バルク)の2つのモードがあります。非増分は、単にシステムの新しい動的構成を指定します。増分は、現在の構成への変更を指定します。 reconfig
コマンドは、新しい構成を返します。
いくつかの例は、*ReconfigTest.java*、*ReconfigRecoveryTest.java*、および *TestReconfigServer.cc* にあります。
一般
サーバーの削除: リーダーを含む任意のサーバーを削除できます(リーダーを削除すると、短時間の可用性の低下が発生します。論文 の図6と8を参照)。サーバーは自動的にシャットダウンされません。代わりに、「投票権のないフォロワー」になります。これは、投票が操作をコミットするために必要な投票の定足数にカウントされないという点で、オブザーバーと somewhat 似ています。ただし、投票権のないフォロワーとは異なり、オブザーバーは実際には操作の提案を確認せず、ACKもしません。したがって、投票権のないフォロワーは、オブザーバーと比較して、システムスループットに大きな悪影響を及ぼします。投票権のないフォロワーモードは、サーバーをシャットダウンする前、またはフォロワーまたはオブザーバーとしてアンサンブルに追加する前に、一時的なモードとしてのみ使用する必要があります。サーバーを自動的にシャットダウンしない主な理由は2つあります。最初の理由は、このサーバーに接続されているすべてのクライアントがすぐに切断され、他のサーバーへの接続要求が殺到することを避けたいからです。代わりに、各クライアントがいつ独立して移行するかを決定する方が良いでしょう。2番目の理由は、サーバーを削除することが、サーバーを「オブザーバー」から「参加者」に変更するために(まれに)必要な場合があるためです(追加のコメントセクションで説明されています)。
新しい構成は、正当と見なされるために、最小限の数の参加者を持つ必要があることに注意してください。提案された変更により、クラスタの参加者が2人未満になり、スタンドアロンモードが有効になっている場合(standaloneEnabled=true、 *standaloneEnabled* フラグセクションを参照)、再構成は処理されません(BadArgumentsException)。スタンドアロンモードが無効になっている場合(standaloneEnabled=false)、1人以上の参加者でいることは合法です。
**サーバーの追加:** 再構成が呼び出される前に、管理者は、新しい構成の参加者の過半数(過半数)がすでに現在のリーダーに接続され、同期されていることを確認する必要があります。これを達成するには、新しい参加サーバーを正式にアンサンブルの一部にする前に、リーダーに接続する必要があります。これは、技術的にはシステムの正当な構成ではないが、(a)参加者を含み、(b)現在のリーダーを見つけて接続するために参加者に十分な情報を提供するサーバーの初期リストを使用して、参加サーバーを起動することによって行われます。これを安全に行うためのいくつかの異なるオプションをリストします。
- 参加者の初期構成は、最後にコミットされた構成のサーバーと1人以上の参加者で構成され、**参加者はオブザーバーとしてリストされます。** たとえば、サーバーDとEが(A、B、C)に同時に追加され、サーバーCが削除されている場合、Dの初期構成は(A、B、C、D)または(A、B、C、D、E)にすることができ、DとEはオブザーバーとしてリストされます。同様に、Eの構成は(A、B、C、E)または(A、B、C、D、E)にすることができ、DとEはオブザーバーとしてリストされます。**参加者をオブザーバーとしてリストしても、実際にはオブザーバーにならないことに注意してください。他の参加者と誤って定足数を形成するのを防ぐだけです。** 代わりに、現在の構成のサーバーに接続し、参加者がいない最後にコミットされた構成(A、B、C)を採用します。これが発生すると、参加者の構成ファイルはバックアップされ、自動的に置き換えられます。現在のリーダーに接続した後、参加者は、システムが再構成され、アンサンブルに追加される(参加者またはオブザーバーとして、適切な場合)まで、投票権のないフォロワーになります。
- 各参加者の初期構成は、最後にコミットされた構成のサーバー+ **参加者としてリストされた参加者自体** で構成されます。たとえば、新しいサーバーDをサーバー(A、B、C)で構成される構成に追加するには、管理者はサーバー(A、B、C、D)で構成される初期構成ファイルを使用してDを起動できます。DとEの両方が(A、B、C)に同時に追加される場合、Dの初期構成は(A、B、C、D)にすることができ、Eの構成は(A、B、C、E)にすることができます。同様に、Dが追加され、Cが同時に削除される場合、Dの初期構成は(A、B、C、D)にすることができます。初期構成に複数の参加者を参加者としてリストしないでください(以下の警告を参照)。
- 参加者をオブザーバーまたは参加者としてリストするかどうかは、現在のリーダーがリストに含まれている限り、すべての現在の構成サーバーをリストしないことも問題ありません。たとえば、Dを追加する場合、Aが現在のリーダーであれば、(A、D)のみで構成される構成ファイルを使用してDを起動できます。ただし、これはより脆弱です。Dが正式にアンサンブルに参加する前にAが失敗した場合、Dは他の誰を知らないため、管理者は介入して別のサーバーリストでDを再起動する必要があります。
注記
警告
同じ初期構成に複数の参加サーバーを参加者として指定しないでください。現在、参加サーバーは既存のアンサンブルに参加していることを知りません。複数の参加者が参加者としてリストされている場合、メインアンサンブルとは独立して操作を処理するなど、独立した定足数を形成してスプリットブレインの状況が発生する可能性があります。初期構成に複数の参加者をオブザーバーとしてリストすることは問題ありません。
参加者が接続して構成の変更について学習する前に、既存のサーバーの構成が変更されたり、使用できなくなった場合、接続できるように、更新された構成ファイルを使用して参加者を再起動する必要がある場合があります。
最後に、リーダーに接続されると、参加者は最後にコミットされた構成を採用し、その構成には存在しません(参加者の初期構成は書き換えられる前にバックアップされます)。参加者がこの状態で再起動すると、構成ファイルに存在しないため、起動できません。起動するには、もう一度初期構成を指定する必要があります。
**サーバーパラメータの変更:** サーバーのポート、またはそのロール(参加者/オブザーバー)を、異なるパラメータでアンサンブルに追加することにより変更できます。これは、増分再構成モードとバルクレ構成モードの両方で機能します。サーバーを削除してから 다시 追加する必要はありません。サーバーがまだシステムにないかのように、新しいパラメータを指定するだけです。サーバーは構成の変更を検出し、必要な調整を実行します。増分モードセクションの例と、このルールの例外については、追加のコメントセクションを参照してください。
アンサンブルで使用されるクォーラムシステムを変更することもできます(たとえば、過半数クォーラムシステムを階層クォーラムシステムにオンザフライで変更します)。ただし、これはバルク(非増分)再構成モードを使用する場合にのみ許可されます。一般に、増分再構成は過半数クォーラムシステムでのみ機能します。バルクレ構成は、階層クォーラムシステムと過半数クォーラムシステムの両方で機能します。
**パフォーマンスへの影響:** フォロワーを削除しても、自動的にシャットダウンされないため、パフォーマンスへの影響は事実上ありません(削除の効果は、サーバーの投票がカウントされなくなることです)。サーバーを追加する場合、リーダーの変更はなく、顕著なパフォーマンスの低下もありません。詳細とグラフについては、論文 の図6、7、および8を参照してください。
最も重大な混乱は、次のいずれかの場合にリーダーの変更が発生した場合に発生します
- リーダーがアンサンブルから削除されます。
- リーダーのロールが参加者からオブザーバーに変更されます。
- リーダーがトランザクションを他のユーザーに送信するために使用するポート(クォーラムポート)が変更されます。
このような場合、古いリーダーが新しいリーダーを指名するリーダーのハンドオフを実行します。 結果として生じる可用性の低下は、通常、リーダーのクラッシュ時よりも短くなります。これは、リーダーの障害検出が不要であり、ハンドオフ中に新しいリーダーの選出を回避できるためです(論文の図6および8を参照)。
サーバーのクライアントポートが変更された場合、既存のクライアント接続は切断されません。 サーバーへの新しい接続は、新しいクライアントポートを使用する必要があります。
進捗保証: 再構成操作の呼び出しまで、ZooKeeperが進捗できるように、古い構成のクォーラムが利用可能で接続されている必要があります。 再構成が呼び出されると、古い構成と新しい構成の両方のクォーラムが利用可能である必要があります。 最終的な移行は、(a)新しい構成がアクティブ化され、(b)リーダーによって新しい構成がアクティブ化される前にスケジュールされたすべての操作がコミットされると発生します。 (a)と(b)が発生すると、新しい構成のクォーラムのみが必要になります。 ただし、(a)も(b)もクライアントには表示されないことに注意してください。 具体的には、再構成操作がコミットされると、アクティベーションメッセージがリーダーによって送信されたことのみを意味します。 必ずしも、新しい構成のクォーラムがこのメッセージを受信した(アクティブ化するために必要)こと、または(b)が発生したことを意味するわけではありません。 (a)と(b)の両方がすでに発生したことを確認したい場合(たとえば、削除された古いサーバーをシャットダウンしても安全であることを確認するため)、更新(set-data
、またはその他のクォーラム操作、ただしsync
ではない)を呼び出して、コミットされるまで待機するだけです。 これを達成する別の方法は、再構成プロトコルに別のラウンドを追加することでした(Zabとのシンプルさと互換性のために、これを回避することにしました)。
増分モード
増分モードでは、現在の構成にサーバーを追加および削除できます。 複数の変更が可能です。 例:
> reconfig -remove 3 -add
server.5=125.23.63.23:1234:1235;1236
追加オプションと削除オプションはどちらも、コンマ区切りの引数のリストを取得します(スペースなし)。
> reconfig -remove 3,4 -add
server.5=localhost:2111:2112;2113,6=localhost:2114:2115:observer;2116
サーバーステートメントの形式は、クライアントポートの指定セクションで説明されているものとまったく同じで、クライアントポートが含まれています。 ここでは、"server.5="の代わりに"5="と記述できることに注意してください。 上記の例では、サーバー5がすでにシステムに存在するが、ポートが異なる場合、またはオブザーバーでない場合は、更新され、構成がコミットされるとオブザーバーになり、これらの新しいポートの使用を開始します。 これは、サーバーを再起動せずに、参加者をオブザーバーに、またはその逆にしたり、ポートを変更したりする簡単な方法です。
ZooKeeperは、2種類のクォーラムシステムをサポートしています。単純な多数決システム(リーダーは、投票者の過半数からACKを受信した後に操作をコミットする)と、より複雑な階層型システム(異なるサーバーの投票の重みが異なり、サーバーが投票グループに分割される)です。 現在、増分再構成は、リーダーに認識されている最後に提案された構成が多数決クォーラムシステムを使用している場合にのみ許可されます(それ以外の場合はBadArgumentsExceptionがスローされます)。
増分モード - Java APIを使用した例
List<String> leavingServers = new ArrayList<String>();
leavingServers.add("1");
leavingServers.add("2");
byte[] config = zk.reconfig(null, leavingServers, null, -1, new Stat());
List<String> leavingServers = new ArrayList<String>();
List<String> joiningServers = new ArrayList<String>();
leavingServers.add("1");
joiningServers.add("server.4=localhost:1234:1235;1236");
byte[] config = zk.reconfig(joiningServers, leavingServers, null, -1, new Stat());
String configStr = new String(config);
System.out.println(configStr);
非同期APIと、Listの代わりにコンマ区切りの文字列を受け入れるAPIもあります。
非増分モード
再構成の2番目のモードは非増分モードであり、クライアントは新しい動的システム構成の完全な仕様を提供します。 新しい構成は、その場で指定することも、ファイルから読み取ることもできます。
> reconfig -file newconfig.cfg
//newconfig.cfgは動的構成ファイルです。 動的構成ファイルを参照してください。
> reconfig -members
server.1=125.23.63.23:2780:2783:participant;2791,server.2=125.23.63.24:2781:2784:participant;2792,server.3=125.23.63.25:2782:2785:participant;2793}}
新しい構成では、異なるクォーラムシステムを使用できます。 たとえば、現在のアンサンブルが多数決クォーラムシステムを使用している場合でも、階層型クォーラムシステムを指定できます。
一括モード - Java APIを使用した例
List<String> newMembers = new ArrayList<String>();
newMembers.add("server.1=1111:1234:1235;1236");
newMembers.add("server.2=1112:1237:1238;1239");
newMembers.add("server.3=1114:1240:1241:observer;1242");
byte[] config = zk.reconfig(null, null, newMembers, -1, new Stat());
String configStr = new String(config);
System.out.println(configStr);
非同期APIと、Listの代わりに新しいメンバーを含むコンマ区切りの文字列を受け入れるAPIもあります。
条件付き再構成
場合によっては(特に非増分モード)、新しく提案された構成は、クライアントが現在の構成であると「信じている」ものによって異なり、その構成にのみ適用する必要があります。 具体的には、リーダーの最後の構成に指定されたバージョンがある場合にのみ、reconfig
は成功します。
> reconfig -file <filename> -v <version>
上記のJavaの例では、-1の代わりに構成バージョンを指定して、再構成を条件付けることができます。
エラー状態
通常のZooKeeperエラー状態に加えて、再構成は次の理由で失敗する可能性があります。
- 別の再構成が現在進行中です(ReconfigInProgress)。
- 提案された変更により、スタンドアロンモードが有効な場合、クラスターの参加者が2人未満になります。スタンドアロンモードが無効な場合は、1人以上の参加者でいることが合法です(BadArgumentsException)。
- 再構成処理の開始時に、新しい構成のクォーラムが接続されておらず、リーダーと最新の状態ではありませんでした(NewConfigNoQuorum)。
-v x
が指定されましたが、最新構成のバージョンy
はx
ではありません(BadVersionException)。- 増分再構成が要求されましたが、リーダーの最後の構成は多数決システムとは異なるクォーラムシステムを使用しています(BadArgumentsException)。
- 構文エラー(BadArgumentsException)。
- ファイルから構成を読み取るときのI/O例外(BadArgumentsException)。
これらのほとんどは、*ReconfigFailureCases.java*のテストケースで示されています。
追加コメント
活性: 増分再構成と非増分再構成の違いを理解するために、クライアントC1がサーバーDをシステムに追加し、別のクライアントC2がサーバーEを追加するとします。非増分モードでは、各クライアントは最初にconfig
を呼び出して現在の構成を調べ、次に独自の推奨サーバーを追加することによってローカルで新しいサーバーリストを作成します。 その後、非増分reconfig
コマンドを使用して、新しい構成を送信できます。 両方の再構成が完了した後、最後にリーダーに到着したクライアントのリクエストに応じて、EまたはDのいずれか一方のみが追加されます(両方ではありません)。これは、以前の構成を上書きします。 もう一方のクライアントは、変更が有効になるまでプロセスを繰り返すことができます。 この方法は、システム全体の進捗(つまり、クライアントの1つ)を保証しますが、すべてのクライアントが成功することを保証するものではありません。 条件付き再構成セクションで説明されているように、C2は、現在の構成のバージョンが変更されていない場合にのみ再構成を実行するように要求できます。 この方法では、C1の構成が最初にリーダーに到達した場合、C1の構成を盲目的に上書きすることを回避できます。
増分再構成では、両方の変更が有効になります。これは、リーダーによって現在の構成に順番に適用されるためです(2番目の再構成リクエストが、リーダーが最初の再構成リクエストのコミットメッセージを送信した後にリーダーに到達すると仮定します。現在、リーダーは、別の再構成がすでに保留中の場合、再構成の提案を拒否します)。 両方のクライアントが進捗することが保証されているため、この方法はより強力な活性度を保証します。 実際には、複数の同時再構成はまれです。 非増分再構成は、現在、クォーラムシステムを動的に変更する唯一の方法です。 増分構成は、現在、多数決クォーラムシステムでのみ許可されています。
オブザーバーをフォロワーに変更する: 明らかに、投票に参加するサーバーをオブザーバーに変更すると、エラー(2)が発生した場合、つまり、最小許容数の参加者未満が残っている場合に失敗する可能性があります。 ただし、オブザーバーを参加者に変換すると、より微妙な理由で失敗することがあります。たとえば、現在の構成が(A、B、C、D)であり、Aがリーダー、BとCがフォロワー、Dがオブザーバーであるとします。 さらに、Bがクラッシュしたとします。 Dがフォロワーになるとされる再構成が送信されると、この構成では、新しい構成の投票者の過半数(任意の3人の投票者)がリーダーと接続され、最新の状態になっている必要があるため、エラー(3)で失敗します。 オブザーバーは、再構成中に送信された履歴プレフィックスを承認できないため、これらの3つの必須サーバーとしてカウントされず、再構成は中止されます。 このような場合は、クライアントは2つの再構成コマンドで同じタスクを実行できます。最初に再構成を呼び出してDを構成から削除し、次に2番目のコマンドを呼び出して参加者(フォロワー)として追加し直します。 中間状態の間、Dは投票権のないフォロワーであり、2番目の再構成コマンド中に実行される状態転送を承認できます。
クライアント接続のリバランス
ZooKeeperクラスターが起動されたときに、各クライアントに同じ接続文字列(サーバーのリスト)が指定されている場合、クライアントはリスト内のサーバーをランダムに選択して接続します。これにより、サーバーあたりのクライアント接続の予想数が各サーバーで同じになります。 再構成によってサーバーのセットが変更されたときに、このプロパティを保持するメソッドを実装しました。 論文のセクション4および5.1を参照してください。
メソッドが機能するためには、すべてのクライアントが構成の変更をサブスクライブする必要があります(/zookeeper/configで直接、またはgetConfig
APIコマンドを介してウォッチを設定することにより)。 ウォッチがトリガーされたら、クライアントはsync
とgetConfig
を呼び出して新しい構成を読み取り、構成が実際に新しい場合はupdateServerList
APIコマンドを呼び出す必要があります。 同時にクライアントの大量移行を回避するには、各クライアントがupdateServerList
を呼び出す前にランダムな短時間スリープすることをお勧めします。
いくつかの例は、*StaticHostProviderTest.java*と*TestReconfig.cc*にあります。
例(これはレシピではなく、一般的な考え方を説明するための簡略化された例です)。
public void process(WatchedEvent event) {
synchronized (this) {
if (event.getType() == EventType.None) {
connected = (event.getState() == KeeperState.SyncConnected);
notifyAll();
} else if (event.getPath()!=null && event.getPath().equals(ZooDefs.CONFIG_NODE)) {
// in prod code never block the event thread!
zk.sync(ZooDefs.CONFIG_NODE, this, null);
zk.getConfig(this, this, null);
}
}
}
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
if (path!=null && path.equals(ZooDefs.CONFIG_NODE)) {
String config[] = ConfigUtils.getClientConfigStr(new String(data)).split(" "); // similar to config -c
long version = Long.parseLong(config[0], 16);
if (this.configVersion == null){
this.configVersion = version;
} else if (version > this.configVersion) {
hostList = config[1];
try {
// the following command is not blocking but may cause the client to close the socket and
// migrate to a different server. In practice it's better to wait a short period of time, chosen
// randomly, so that different clients migrate at different times
zk.updateServerList(hostList);
} catch (IOException e) {
System.err.println("Error updating server list");
e.printStackTrace();
}
this.configVersion = version;
}
}
}