状態ストア プロトコル

状態ストアは、Azure IoT Operations クラスター内の分散ストレージ システムです。 状態ストアでは、MQTT ブローカーの MQTT メッセージと同じ高可用性の保証が提供されます。 MQTT5/RPC プロトコルのガイドラインに従って、クライアントは MQTT v5 を使用して状態ストアと対話する必要があります。 この記事では、独自の状態ストア クライアントを実装する必要がある開発者向けのプロトコル ガイダンスを提供します。

概要

状態ストアでは、次のコマンドがサポートされています。

  • SET <keyName><keyValue><setOptions>
  • GET <キー名>
  • DEL <キー名>
  • VDEL <keyName><keyValue> ## 所定の <keyName> を削除します。これは、値が <keyValue> である場合にのみ行います

このプロトコルでは、次の要求応答モデルが使用されます。

  • 要求。 クライアントは、明確に定義された状態ストア システム トピックに要求を発行します。 要求を発行するために、クライアントは、次のセクションで説明する必要なプロパティとペイロードを使用します。
  • 応答。 状態ストアは、要求を非同期的に処理し、クライアントが最初に指定した応答トピックで応答します。

次の図は、要求と応答の基本的なビューを示しています。

状態ストアの基本的な要求と応答のプロセスの図。

状態ストア のシステム トピック、QoS、および必要な MQTT v5 プロパティ

状態ストアと通信するには、クライアントが次の要件を満たしている必要があります。

  • MQTT v5 を使用します。 詳細については、 MQTT v5 の仕様を参照してください。
  • QoS 1 (サービス品質レベル 1) を使用します。 QoS 1 は MQTT v5 仕様で説明されています。
  • MQTT ブローカーのクロックから 1 分以内のクロックがあります。

状態ストアと通信するには、PUBLISHクライアントがシステム トピックに要求statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/command/invokeする必要があります。 状態ストアは Azure IoT Operations の一部であるため、起動時に暗黙的 SUBSCRIBE をこのトピックに対して行います。

要求をビルドするには、次の MQTT5 プロパティが必要です。 これらのプロパティが存在しない場合、または要求が QoS 1 型でない場合、要求は失敗します。

  • 応答トピック。 状態ストアは、この値を使用して初期要求に応答します。 ベスト プラクティスとして、応答トピックを clients/{clientId}/services/statestore/_any_/command/invoke/response のように書式設定します。 応答トピックを statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/command/invoke、または clients/statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8 で始まるものとして設定することは、状態ストアの要求では許可されません。 状態ストアでは、無効な応答トピックを使用する MQTT クライアントを切断します。
  • 相関関係データ。 状態ストアが応答を送信すると、初期要求の関連付けデータが含まれます。

次の図は、要求と応答の展開ビューを示しています。

状態ストアの拡張された要求と応答のプロセスの図。

サポートされているコマンド

The commands SET コマンド、GET コマンド、DEL コマンドは、予期したとおりに動作します。

SET コマンドが設定し、GET コマンドが取得する値は、任意のバイナリ データです。 値のサイズは、MQTT ペイロードの最大サイズと、MQTT ブローカーとクライアントのリソース制限によってのみ制限されます。

SET のオプション

SET コマンドは、基本 keyValue フラグと keyName フラグ以外にも、次のようなオプションのフラグを提供します。

  • NX。 キーがまだ存在しない場合にのみ、キーを設定できるようにします。
  • NEX <value>。 キーが存在しない場合、またはキーの値が既に < 値>に設定されている場合にのみ、キーを設定できるようにします。 NEX フラグは通常、クライアントがキーの有効期限 (PX) を更新するために使用されます。
  • PX。 キーの有効期限が切れるまでの期間 (ミリ秒単位)。

VDEL のオプション

VDEL コマンドは、DEL このコマンドの特殊なケースです。 DEL は無条件で指定の keyName を削除します。 VDEL には、keyValue という別の引数が必要です。 VDEL は、指定の keyName を、同じ keyValue を持っている場合にのみ削除します。

ペイロード形式

状態ストア PUBLISH のペイロード形式は、RESP3 に着想を得た形式です。これは、Redis が使用する基になるプロトコルです。 RESP3 は、SET や GET などの動詞と、keyName や keyValue などのパラメーターの両方をエンコードします。

大文字と小文字の区別

クライアントは、動詞とオプションの両方を大文字で送信する必要があります。

要求の形式

要求は、次の例のように書式設定されます。 RESP3 に続いて、* は配列内の項目の数を表します。 $ 文字は、後続の CRLF を除く、次の行の文字数です。

RESP3 形式でサポートされているコマンドは、GET、SET、DEL、VDEL です。

*{NUMBER-OF-ARGUMENTS}<CR><LF>
${LENGTH-OF-NEXT-LINE}<CR><LF>
{COMMAND-NAME}<CR><LF>
${LENGTH-OF-NEXT-LINE}<CR><LF> // This is always the keyName with the current supported verbs.
{KEY-NAME}<CR><LF>
// Next lines included only if command has additional arguments
${LENGTH-OF-NEXT-LINE}<CR><LF> // This is always the keyValue for set
{KEY-VALUE}<CR><LF>

次の出力例は、状態ストア RESP3 ペイロードを示しています。

*3<CR><LF>$3<CR><LF>set<CR><LF>$7<CR><LF>SETKEY2<CR><LF>$6<CR><LF>VALUE5<CR><LF>
*2<CR><LF>$3<CR><LF>get<CR><LF>$7<CR><LF>SETKEY2<CR><LF>
*2<CR><LF>$3<CR><LF>del<CR><LF>$7<CR><LF>SETKEY2<CR><LF>
*3<CR><LF>$4<CR><LF>vdel<CR><LF>$7<CR><LF>SETKEY2<CR><LF>$3<CR><LF>ABC<CR><LF>

注

SET には、「バージョン管理とハイブリッド論理クロック」セクションで説明されているように、MQTT5 の追加のプロパティが必要です。

応答形式

状態ストアが無効な RESP3 ペイロードを検出しても、要求元の Response Topicに対する応答が返されます。 無効なペイロードの例としては、無効なコマンド、無効な RESP3、整数オーバーフローなどがあります。 無効なペイロードは文字列 -ERR で始まり、詳細が含まれています。

注

存在しないキーに対する GET、DEL、VDEL 要求は、エラーとは見なされません。

クライアントが無効なペイロードを送信した場合、状態ストアは次の例のようなペイロードを送信します。

-ERR syntax error

SET の応答

SET 要求が成功すると、状態ストアは次のペイロードを返します。

+OK<CR><LF>

NX または NEX 設定オプションで、キーを設定できないことを意味する条件チェックが指定されているために SET 要求が失敗した場合、状態ストアは次のペイロードを返します。

-1<CR><LF>

GET の応答

存在しないキーに対して GET 要求が行われると、状態ストアは次のペイロードを返します。

$-1<CR><LF>

キーが見つかると、状態ストアは次の形式で値を返します。

${NumberOfBytes}<CR><LF>
{KEY-VALUE}

1234 値を返す状態ストアの出力は、次の例のようになります。

$4<CR><LF>1234<CR><LF>

DEL と VDEL 応答

状態ストアは、削除要求で削除した値の数を返します。 現在、状態ストアで削除できる値は一度に 1 つだけです。

:{NumberOfDeletes}<CR><LF> // Will be 1 on successful delete or 0 if the keyName is not present

成功した DEL コマンドの例を次に示します。

:1<CR><LF>

指定された値がキーに関連付けられた値と一致しないために VDEL 要求が失敗した場合、状態ストアは次のペイロードを返します。

-1<CR><LF>

-ERR の応答

エラー文字列の現在の一覧を次に示します。 クライアント アプリケーションは、状態ストアの更新をサポートするために、"不明なエラー" 文字列を処理する必要があります。

状態ストアから返されるエラー文字列 説明
要求タイムスタンプは、相当な未来の時刻です。クライアントとブローカーのシステム クロックが同期されていることを確認してください 状態ストアとクライアントのクロックが同期されていないために発生する不測の要求タイムスタンプ。
この要求にはフェンス トークンが必要です キーがフェンス トークンでマークされているが、そのフェンス トークンがクライアントで指定されていない場合に発生するエラー。
フェンス トークンの要求タイムスタンプは、相当な未来の時刻です。クライアントとブローカーのシステム クロックが同期されていることを確認してください 状態ストアとクライアントのクロックが同期されていないために発生する予期しないフェンス トークンのタイムスタンプ。
要求フェンス トークンが、リソースを保護するフェンス トークンよりも低いバージョンです 要求フェンス トークンのバージョンが正しくありません。 詳細については、「バージョン管理とハイブリッド論理クロック」 (#versioning-and-hybrid-logical-clocks) を参照してください。
クォータを超えました 状態ストアには、それに格納できるキーの数のクォータがあり、このクォータは、指定された MQTT ブローカーのメモリ プロファイルに基づいています。
構文エラー 送信されたペイロードが、状態ストアの定義に準拠していません。
承認されていません 認証エラー
不明なコマンド コマンドが認識されません。
誤った数の引数 想定されている引数の数が正しくありません。
タイムスタンプがありません クライアントで SET を実行する場合、そのタイムスタンプを表す HLC として MQTT5 ユーザー プロパティ _ts を設定する必要があります。
形式に誤りがあるタイムスタンプ _ts またはフェンス トークンのタイムスタンプが不正です。
キーの長さがゼロです 状態ストアでは、キーの長さをゼロにすることはできません。

バージョン管理とハイブリッド論理クロック

このセクションでは、状態ストアがバージョン管理を処理する方法について説明します。

ハイブリッド論理クロックとしてのバージョン

状態ストアは、格納する各値のバージョンを保持します。 状態ストアでは、単調に増加するカウンターを使用してバージョンを維持できます。 代わりに、状態ストアはハイブリッド論理クロック (HLC) を使用してバージョンを表します。 詳細については、HLC の元の設計に関する記事と、HLC の背後にある意図に関する記事を参照してください。

状態ストアでは、次の形式を使用して HLC を定義します。

{wallClock}:{counter}:{node-Id}

wallClock は Unix エポック以降のミリ秒数です。 counter と node-Id は、一般的に HLC として機能します。

クライアントは SET を実行する場合、クライアントの現在のクロックに基づいて、そのタイムスタンプを表す HLC として __ts MQTT5 ユーザー プロパティを設定する必要があります。 状態ストアは、応答メッセージ内の値のバージョンを返します。 応答は HLC としても指定され、__ts MQTT5 ユーザー プロパティも使用されます。 返される HLC は、最初の要求の HLC より常に大きくなります。

値のバージョンの設定と取得の例

このセクションでは、値のバージョンの設定と取得の例を示します。

クライアントは keyName=value を設定します。 クライアント クロックは 10 月 3 日午後 11:07:05 GMT です。 Unix エポック以降、クロック値は 1696374425000 ミリ秒です。 状態ストアのシステム クロックがクライアント システム クロックと同じであるとします。 クライアントは、前述のように SET コマンドを実行します。

次の図は、SET コマンドを示しています。

値のバージョンを設定する状態ストア コマンドの図。

初期セットの __ts (タイムスタンプ) プロパティには、クライアント ウォール クロックとしての 1696374425000、0 としてのカウンター、CLIENT としてのノード ID が含まれます。 応答では、状態ストアが返す __ts プロパティには、1 ずつインクリメントされるカウンター wallClock と、ノード ID が StateStore として含まれます。 状態ストアは、HLC 更新の動作方法に基づいて、クロックが先行している場合は、より高い wallClock 値を返す可能性があります。

このバージョンは、成功した GET、DEL、VDEL の各要求でも返されます。 これらの要求では、クライアントは __ts を指定しません。

次の図は、GET コマンドを示しています。

値のバージョンを取得する状態ストアの図。

注

状態ストアから返されるタイムスタンプ __ts は、最初の SET 要求で返されたものと同じです。

特定のキーが後で新しい SET で更新された場合、プロセスは似ています。 クライアントは、現在のクロックに基づいて要求 __ts を設定する必要があります。 状態ストアは値のバージョンを更新し、HLC 更新規則に従って __ts を返します。

時刻のずれ

状態ストアは、状態ストアのローカル クロックより 1 分以上前の __ts (および __ft) を拒否します。

状態ストアは、状態ストアのローカル クロックの背後にある __ts を受け入れます。 HLC アルゴリズムで指定されているように、状態ストアはキーのバージョンをローカル クロックに設定します。これは、こちらのほうが大きいためです。

ロック トークンとフェンス トークン

このセクションでは、ロック トークンとフェンス トークンの目的と使用方法について説明します。

バックグラウンド

状態ストアを使用する MQTT クライアントが 2 つ以上あるとします。 どちらのクライアントも、特定のキーに書き込もうとします。 状態ストア クライアントには、一度に 1 つのクライアントのみが特定のキーを変更できるように、キーをロックするメカニズムが必要です。

このシナリオの例は、アクティブ システムとスタンバイ システムで発生します。 両方とも同じ処理を実行する 2 つのクライアントがあり、この処理に同じ状態ストア キーのセットが含まれる可能性があります。 特定の時点で、いずれかのクライアントがアクティブであり、そのアクティブなシステムが停止またはクラッシュしたときにすぐに引き継ぐように、もう一方のクライアントがスタンバイしています。 理想的には、一度に 1 つのクライアントのみが、状態ストアに書き込むようにする必要があります。 ただし、分散システムでは、両方のクライアントがアクティブであるかのように動作し、同時に同じキーへの書き込みを試みる可能性があります。 このようなシナリオでは、競合状態が生じます。

状態ストアには、フェンス トークンを使用してこの競合状態を防ぐメカニズムが用意されています。 フェンス トークンと、競合状態から保護するために設計されたクラスの詳細については、この記事を参照してください。

フェンス トークンの取得

この例では、次の要素があることを前提としています。

  • Client1 と Client2. これらのクライアントは、アクティブとスタンバイのペアとして機能する状態ストア クライアントです。
  • LockName。 ロックとして機能する状態ストア内のキーの名前。
  • ProtectedKey。 複数の書き込み元から保護する必要があるキー。

クライアントは、最初の手順としてロックを取得しようとします。 これらは SET LockName {CLIENT-NAME} NEX PX {TIMEOUT-IN-MILLISECONDS} を行うことによって、ロックを取得します。 Set オプションで NEX フラグは、次のいずれかの条件が満たされた場合にのみ SET が成功するのを意味することを思い出してください。

  • このキーが空です
  • このキーの値は既に<値>に設定されており、PX はタイムアウトをミリ秒単位で指定しています。

Client1 が最初に SET LockName Client1 NEX PX 10000 の要求を受け取るとします。 この要求により、LockName の所有権が 10,000 ミリ秒で付与されます。 Client2 が SET LockName Client2 NEX ... を試行したときに、Client1 がロックを所有していた場合、NEX フラグは、Client2 要求が失敗することを意味します。 Client1 は、ロックの取得に使用したのと同じ SET コマンドを送信して、このロックを更新する必要があります (Client1 が引き続き所有権を持つ場合)。

注

SET NX は概念的には AcquireLock() と同等です。

SET 要求でのフェンス トークンの使用

Client1 が SET に対して LockName ("AcquireLock") を正常に行うと、状態ストアは、MQTT5 ユーザー プロパティ LockName でハイブリッド論理クロック (HLC) として __ts のバージョンを返します。

クライアントは、SET 要求を実行するときに、必要に応じて "フェンス トークン" を表すための MQTT5 ユーザー プロパティ __ft を含むことができます。 __ft は HLC として表されます。 特定のキーと値のペアに関連付けられているフェンス トークンは、ロックの所有権チェックを提供します。 フェンス トークンはどこからでも取得できます。 このシナリオでは、LockName のバージョンから取得する必要があります。

次の図は、Client1 で SET 要求を実行する LockName プロセスを示しています。

ロック名プロパティで SET 要求を実行しているクライアントの図。

次に、Client1 は、要求内の __ts プロパティの基礎として変更されていない Property=1696374425000:1:StateStore プロパティ (__ft) を使用して、ProtectedKey を変更します。 すべての SET 要求と同様に、クライアントは __ts の ProtectedKey プロパティを設定する必要があります。

次の図は、Client1 で SET 要求を実行する ProtectedKey プロセスを示しています。

保護されたキー プロパティで SET 要求を実行しているクライアントの図。

要求が成功した場合、ProtectedKey のこの時点から、SET 要求で指定されたトークン以上のフェンシング トークンが必要です。

フェンス トークン アルゴリズム

値が時刻のずれの最大値内である場合、状態ストアはキーと値のペアの __ts のために HLC を受け入れます。 ただし、フェンス トークンについても同じことが当てはまるわけではありません。

フェンス トークンの状態ストア アルゴリズムは次のとおりです。

  • キーと値のペアに関連付けられたフェンス トークンがなく、SET 要求が __ft を設定する場合、状態ストアは、関連付けられた __ft をキーと値のペアと一緒に格納します。
  • キーと値のペアにフェンス トークンが関連付けられている場合:
    • SET 要求で __ft が指定されていない場合、要求を拒否します。
    • SET 要求で、キーと値のペアに関連付けられているフェンス トークンよりも古い HLC 値を持つ __ft が指定されている場合は、要求を拒否します。
    • SET 要求で、キーと値のペアに関連付けられているフェンシング トークンと等しいか新しい HLC 値を持つ __ft が指定されている場合は、要求を受け入れます。 状態ストアは、キーと値のペアが新しい場合は、そのフェンス トークンを更新して、要求に設定されるトークンにします。

キーがフェンス トークンでマークされた後、要求が成功するためには、DEL 要求と VDEL 要求でも、__ft プロパティを含める必要があります。 このアルゴリズムは前のものと同じですが、キーが削除されているためフェンス トークンが格納されない点が異なります。

クライアントの動作

これらのロック メカニズムは、クライアントが適切に動作していることに依存します。 前の例では、動作が不適切な Client2 が、LockName を所有できなくても、SET ProtectedKey トークンよりも新しいフェンス トークンを選択して ProtectedKey を正常に実行できました。 状態ストアは、LockName と ProtectedKey にリレーションシップがあるかどうかを認識していません。 その結果、状態ストアでは、Client2 が実際に値を所有していることの検証は実行されません。

クライアントが、実際にロックを所有しているわけではないキーに書き込めるのは、望ましくない動作です。 クライアントを正しく実装し、認証を使用してキーへのアクセスを信頼されたクライアントのみに制限することで、このようなクライアントの不適切な動作から保護できます。

通知

クライアントは、状態ストアに登録して、変更されているキーの通知を受信できます。 サーモスタットで状態ストア キー {thermostatName}\setPoint が使用されるシナリオについて考えてみます。 他の状態ストア クライアントでは、このキーの値を変更してサーモスタットの setPoint を変更できます。 サーモスタットでは、変更をポーリングするのではなく、状態ストアに登録して、{thermostatName}\setPoint が変更されたときにメッセージを受信できます。

KEYNOTIFY 要求メッセージ

状態ストア クライアントでは、keyName メッセージを送信して、特定の KEYNOTIFY の変更を監視するように状態ストアに要求します。 すべての状態ストア要求と同様に、クライアントは MQTT v5 を介してこのメッセージを含む QoS1 メッセージをステート ストア システム トピック statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/command/invokeに発行します。

要求ペイロードの形式は次のようになります。

KEYNOTIFY<CR><LF>
{keyName}<CR><LF>
{optionalFields}<CR><LF>

場所は:

  • KEYNOTIFY は、コマンドを指定する文字列リテラルです。
  • {keyName} は、通知をリッスンするキー名です。 現在、ワイルドカードはサポートされていません。
  • {optionalFields}: 現在サポートされているオプション フィールドの値は次のとおりです。
    • {STOP}: この要求と同じ keyName と clientId を含む既存の通知がある場合、状態ストアでそれが削除されます。

次の出力例は、キー KEYNOTIFY を監視する SOMEKEY 要求を示しています。

*2<CR><LF>
$9<CR><LF>
KEYNOTIFY<CR><LF>
$7<CR><LF>
SOMEKEY<CR><LF>

KEYNOTIFY 応答メッセージ

すべての状態ストア RPC 要求と同様に、状態ストアでは、その応答を Response Topic に返し、初期要求から指定された Correlation Data プロパティを使用します。 KEYNOTIFY の場合、応答の成功は、状態ストアで要求が処理されたことを示します。 状態ストアでは、要求を正常に処理すると、現在のクライアントのキーを監視するか、監視を停止します。

成功した場合の状態ストアの応答は、成功した SET と同じです。

+OK<CR><LF>

クライアントから KEYNOTIFY SOMEKEY STOP 要求が送信されても、状態ストアでそのキーが監視されていない場合、状態ストアの応答は、存在しないキーの削除を試みるのと同じです。

:0<CR><LF>

その他のエラーは、状態ストアの一般的なエラー報告パターンに従います。

-ERR: <DESCRIPTION OF ERROR><CR><LF>

KEYNOTIFY 通知トピックとライフサイクル

keyName を介して監視されている KEYNOTIFY が変更または削除されると、状態ストアからクライアントに通知が送信されます。 トピックは規則によって決定されます。クライアントでは、KEYNOTIFY プロセス中にトピックは指定されません。

トピックは、次の例で定義されます。 clientId は、KEYNOTIFY 要求を開始したクライアントの MQTT ClientId の大文字の 16 進エンコード表現であり、keyName は、変更されたキーの 16 進エンコード表現です。 このエンコードの場合、状態ストアは「RFC 4648 - The Base16, Base32, and Base64 Data Encodings」の Base 16 エンコード規則に従います。

clients/statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/{clientId}/command/notify/{keyName}

例として、MQTT ブローカーでは、変更されたキー名 NOTIFY を含む、client-id1 に送信される SOMEKEY メッセージをトピックにパブリッシュします。

clients/statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/636C69656E742D696431/command/notify/534F4D454B4559`

通知を使用するクライアントではこのトピックを SUBSCRIBE し、SUBACK 要求の送信 "前" に を受信するように待機して、メッセージが失われないようにする必要があります。KEYNOTIFY

クライアントは、切断された場合、KEYNOTIFY 通知トピックを再サブスクライブし、監視を続ける必要があるキーに対する KEYNOTIFY コマンドを再送信する必要があります。 非クリーン セッション後も保持できる MQTT サブスクリプションとは異なり、状態ストアでは、特定のクライアントが切断されると、KEYNOTIFY メッセージを内部で削除します。

KEYNOTIFY 通知メッセージの形式

KEYNOTIFY を介して監視されているキーが変更されると、状態ストアでは、形式に従ったメッセージを、変更に登録された状態ストア クライアントに対する通知トピックに PUBLISH します。

NOTIFY<CR><LF>
{operation}<CR><LF>
{optionalFields}<CR><LF>

メッセージには次の詳細が含まれます。

  • NOTIFY は、ペイロードに最初の引数として含まれる文字列リテラルであり、通知が到着したことを示します。
  • {operation} は発生したイベントです。 現在、これらの操作は次のとおりです。
    • SET: 値が変更されました。 この操作は、状態ストア クライアントからの SET コマンドの結果としてのみ発生します。
    • DEL: 値が削除されました。 この操作は、状態ストア クライアントからの DEL または VDEL コマンドによってのみ発生します。
  • optionalFields
    • VALUE と {MODIFIED-VALUE}. VALUE は、次のフィールド ({MODIFIED-VALUE}) にキーが変更された後の値が含まれていることを示す文字列リテラルです。 この値は、SET によってキーが変更された場合にのみ応答として送信されます。

次の出力例は、キー SOMEKEY が値 abc に変更されたときに送信される通知メッセージを示しており、初期要求で VALUE オプションが指定されたために GET が含まれています。

*4<CR><LF>
$6<CR><LF>
NOTIFY<CR><LF>
$3<CR><LF>
SET<CR><LF>
$5<CR><LF>
VALUE<CR><LF>
$3<CR><LF>
abc<CR><LF>

KEYNOTIFY 通知メッセージには、SET 要求 (値の更新) についてクライアントに通知されたとき、または DEL または VDEL 要求 (値の削除) についてクライアントに通知されたときの値のタイムスタンプが含まれます。 タイムスタンプは、メッセージの MQTT v5 ユーザー プロパティ __ts の一部に含まれています。 詳細については、セクション「ハイブリッド論理クロックとしてのバージョン」を参照してください。