動作原理
このページでは、クライアントとサーバー(クラスタ)が実際にやり取りするメッセージの流れに 沿ってTicketingの原理を説明します。ワイヤーを流れるバイトの正確な仕様は プロトコル (API)とプロトコル (クラスタ) にあります。
全体構成
ユーザーリクエストを受けるサービス(例: チケット販売開始を処理するイベントサーバー群)が同じ キーでTicketingクラスタに並び、順番が回ってきたサーバーだけが在庫の減算や座席割り当てといった 作業を進める構造です。
ロックの獲得と解放
基本的な流れです。空いているキーは即座に発行され、使用中のキーはFIFOキューで順番を待ちます。 保有者が解放すると、キューの先頭に再競合なしで直接引き継ぎされます — 新しいトークンと 共に即座に移るため、ロックが空く瞬間も、割り込みもありません。
leaseは安全網です。 クライアントが落ちたり解放を忘れたりしても、leaseの時間が 経過すればサーバーがキーを回収し、次の待機者に引き継ぎます。クリティカルセクションの 最悪ケースの所要時間より余裕を持たせて設定してください。v1で許可されるのは1〜250秒のみで、 250秒を超える処理は明示的に未対応として拒否されます。waitは待機時間の上限です。 その時間内に順番が来なければ、ロックを発行せずにT(タイムアウト)で応答するため、ロックが漏れることはありません。0はキューに入らない 1回だけの即時試行です。無限待機はありません。- 同じクライアントが保有中のキーを再度獲得しようとした場合も、他の待機者と同様に キューに並びます — 再入可能(reentrant)なロックではありません。
キー状態ごとのサーバー動作の全ルールはリクエスト × キー状態マトリクスを、 フィールドの値の範囲はフィールド制約を参照してください。
フェンシングトークン
ロックサーバーが完璧であっても、クライアント側の時計までは制御できません。ロックを保持した ままGCの停止や過負荷でしばらく止まり、leaseが失効したことに気づかずに目覚めて作業を 続けてしまう古い保有者は、ロックサーバー単独では防げません。そのため獲得応答のtokenは 発行のたびに必ず前より大きくなるu64整数です。金融用途や永続DBでは、キーごとの high-waterの比較・更新と実際のbusiness writeを、同じDB transactionまたは1つの条件付き writeで原子的に実行する必要があります。fencing rowだけを先に更新し、実際のwriteを後で 行う方式は安全ではありません。
単調性はクラスタ内でも維持されます — トークンの発行番号自体が合意によって複製されるため、 リーダーが交代しても新しいリーダーは常にそれまでより大きい番号から続きを発行します。 トークンフィールドの正確な仕様はフェンシングトークン (スペック) を参照してください。
クラスタ — Raftコンセンサスによる無停止
クラスタモードでは、ノードがRaftコンセンサスによって1つのロック状態を共有します。 リーダーだけがクライアントのリクエストを処理し、すべてのロック状態変更(獲得・解放・失効)は 過半数のノードが記録した後にのみ確定されます。リーダーでないノードに接続すると、 M(移動)応答でリーダーへ案内されます。
- リーダーが落ちると、現在の標準設定では約2.3〜2.5秒で新しいリーダーを選出します。 リクエストのバイトをまだ1つも送っていない場合だけ、残りの
wait予算内で同じ論理acquireを 続行できます。送信後に応答を確認できないacquireはIndeterminateであり、自動再送しません。 leaseの失効すら合意を経由します。 リーダーが失効コマンドをコミットして初めて ロックが消えるため、ノードごとに時計が多少ずれていてもロック状態が食い違うことは ありません。- 過半数を失うと(3台中2台が障害になるなど)、誤ったロックを発行するくらいなら 書き込みを止める側(安全優先) を選びます。3台中2台(または5台中3台)の揮発性process stateを失った場合、そのcluster/fencing domainは復旧せず、同じidentityで自動bootstrap してはいけません。
過半数こそが要であるため、ノード数は3または5のみ許可されます。 偶数はコストが増えるだけで 耐障害性は変わらないため、サーバーは起動を拒否します。
| ノード数 | 無停止 | 同時障害耐性 | 備考 |
|---|---|---|---|
| 1 | ✗ | — | tokenの単調性はprocess lifetime内だけ保証。再起動可能なpersistent DBの保護には未対応 |
| 3 | ✓ | 1 | 無停止の標準構成。 ほとんどの場合これで十分 |
| 5 | ✓ | 2 | 1台メンテナンス中でも冗長性を維持 |
ピアRPCのフレーム・ポートの規則はプロトコル (クラスタ)を、ノードを 1台ずつ交換する手順は無停止の新しいNodeId learner交換 を参照してください。
クライアントの動作
公式クライアントは共通して次のように動作します(言語別のAPIはライブラリを参照)。
- アドレスごとに永続接続を1つバックグラウンドで維持します。アドレスを1つだけ指定した 場合も同じノードへの接続を2本維持するため、片方のソケットが一時的に切れてもサービスは 継続します。
- リクエストを送る接続はリーダー優先、その次はラウンドロビンで選びます。
M(移動)で 案内されたリーダーを記憶しておき、以降のリクエストを直接リーダーへ送ります。 - リクエストはパイプライン化されます。 応答を待たずに次のリクエストを送れます — 対応付けのルールは応答マッチングルールに あります。
- 切断された接続は指数バックオフ(0.1秒 → 最大3.2秒)で再接続を試み続けます。接続が 切れてもブローカーオブジェクトが壊れることはなく、自動的に回復します。
- 同じ論理acquireを内部で再試行するのは、リクエストバイトを1つも送信できなかった場合だけです。 対応付け可能な
Bは確定したBusy(未獲得)として内部再試行せず即時にcallerへ返します。Mは 次の接続用のleader hintにすぎず、owner/keyを持たないため送信済みpendingを再送する根拠には なりません。送信後に応答を確認できないacquireはIndeterminateとして通知し、critical section に入ってはいけません。 - 明示的releaseと既知tokenの補償はowner/token/keyの完全一致とbounded専用queueを使います。 再試行は残りleaseではなく、request/enqueue時点からの絶対5秒deadlineで終了します。成功応答を 得られない明示的releaseを成功として報告・仮定せず、最終的にはserver-side leaseが安全網になります。
接続とセキュリティ
すべての接続(クライアント・ピアともに)はTCP → (TLS) → challenge-response認証の順で 確立されます。サーバーが送った乱数をトークンと合わせてハッシュ化して応答するため、トークンが そのままワイヤー上に流れることはなく、接続のたびに乱数が変わるためリプレイ攻撃もできません。 正確な仕様は認証ハンドシェイクを参照してください。
整合性のまとめ
- 排他制御は合意によって保証されます。 すべての発行が過半数コミットを経るため、 ネットワーク分断やリーダー交代の最中でも、同じキーのロックが2箇所で同時に発行されることは ありません。
- 永続DBではフェンシングを必ず適用してください。
lease失効後に遅れて目覚めたクライアントは、 保護writeと同じtransaction/条件付きwriteで原子的に行うキー別high-water条件だけが防げます。 ロックは順番を整え、DB fencingが最後のstale writeを遮断します。