MQTTリクエスト/レスポンスの解説と例 | MQTT 5機能
この記事では、MQTTの非同期メッセージ配信フレームワーク下でのリクエスト/レスポンスパターンの実装方法について、MQTT 5.0の新機能を活用しながら詳しく解説します。
MQTT 5.0を初めてご利用ですか?ぜひご覧ください
MQTT 5.0以前のリクエスト/レスポンス
MQTTのパブリッシャー/サブスクライバー機構は、メッセージの送信者と受信者を完全にデカップリングし、メッセージを非同期的に配信することを可能にします。しかし、これには問題もあります。QoS 1およびQoS 2メッセージを使用しても、パブリッシャーはメッセージがサーバーに到達したことを保証できますが、サブスクライバーが最終的にメッセージを受信したかどうかを知ることはできません。リクエストやコマンドを実行する際に、パブリッシャーは相手側の実行結果を知りたい場合があります。
最も直接的な方法は、サブスクライバーがリクエストに対してレスポンスを返すことです。
MQTTでは、これは実装が難しくありません。通信する双方が事前にリクエストトピックとレスポンストピックを交渉し、サブスクライバーがリクエストを受信した後にレスポンストピックにレスポンスを返すだけです。これは、MQTT 5.0以前のクライアントが一般的に採用していた方法でもあります。
このスキームでは、レスポンストピックを事前に決定し、柔軟に変更することができません。異なるリクエスターが複数存在する場合、同じレスポンストピックにサブスクライブする必要があるため、すべてのリクエスターがレスポンスを受け取ることになり、レスポンスが自分自身に属するかどうかを判断できません:
この問題を回避する方法はいくつかありますが、ベンダー間で実装が完全に異なる可能性があり、異なるメーカーのデバイスを統合する際のユーザーの難易度と作業負荷が大幅に増加します。
これらの問題を解決するために、MQTT 5.0ではレスポンストピック、コリレーションデータ、レスポンス情報などのプロパティが導入され、MQTTにおけるリクエスト/レスポンスパターンを標準化しました。
MQTT 5.0のリクエスト/レスポンスはどのように機能するか?
プロパティ1 - レスポンストピック
MQTT 5.0では、リクエスターがリクエストメッセージに期待するレスポンストピックを指定できます。リクエストメッセージに基づいて適切なアクションを実行した後、レスポンダーはリクエストに含まれるレスポンストピックにレスポンスメッセージを発行します。リクエスターがそのレスポンストピックにサブスクライブしていれば、レスポンスを受け取ることができます。
リクエスターは、クライアントIDをレスポンストピックの一部として使用することで、異なるリクエスターが意図せず同じレスポンストピックを使用することで発生する競合を効果的に回避できます。
プロパティ2 - コリレーションデータ
リクエスターは、リクエストにコリレーションデータを含めることもでき、レスポンダーはレスポンスにそのコリレーションデータをそのまま返す必要があります。これにより、リクエスターはレスポンスがどのリクエストに属するかを識別できます。
これにより、レスポンダーがリクエストの順序とは異なる順序でレスポンスを返した場合や、ネットワーク切断によりレスポンス(QoS 0)が失われた場合でも、リクエスターがレスポンスを誤ってリクエストに関連付けることを防止できます。
一方で、リクエスターが複数のレスポンダーとやり取りする必要がある場合(例えば、スマートフォンを介して家庭内のさまざまなスマートデバイスを制御する場合など)、コリレーションデータを使用することで、単一のレスポンストピックにサブスクライブしながら、複数のレスポンダーから非同期的に返されるレスポンスを管理できます。
上記のリクエスト/レスポンスプロセスでは、MQTTブローカーはレスポンストピックやコリレーションデータを変更せず、単に転送エージェントとして機能します。
プロパティ3 - レスポンス情報
セキュリティ上の理由から、MQTTサーバーは通常、クライアントが発行およびサブスクライブできるトピックを制限します。リクエスターはランダムなレスポンストピックを指定できますが、そのトピックにサブスクライブする権限があるかどうかを保証できず、レスポンダーがそのレスポンストピックにメッセージを発行する権限があるかどうかも保証できません。
したがって、MQTT 5.0ではレスポンス情報プロパティも導入されました。クライアントは、CONNECTパケット内でレスポンス情報の要求識別子を1に設定することで、サーバーにCONNACKパケット内でレスポンス情報を返すよう要求できます。クライアントは、このレスポンス情報の内容をレスポンストピックの特定部分として使用し、サーバーの権限チェックを通過させることができます。
MQTTは、この部分の詳細(レスポンス情報の内容形式や、クライアントがレスポンス情報に基づいてレスポンストピックをどのように作成するかなど)をさらに指定していないため、異なるサーバーおよびクライアントの実装によって異なる場合があります。
例えば、サーバーはレスポンス情報「FRONT,mytopic」を使用して、レスポンストピックの特定部分の具体的な内容とその位置を示すことができます。または、事前にクライアントとこの特定部分の使用方法について合意し、レスポンス情報「mytopic」を使用してこの部分の具体的な内容のみを示すこともできます。
スマートホームシナリオを例に取ると、スマートデバイスはユーザー間で共有されることはありません。MQTTサーバーがデバイスが属するユーザーのIDをレスポンス情報として返すようにし、クライアントはこのユーザーIDをレスポンストピックのプレフィックスとして一律に使用することができます。MQTTサーバーは、クライアントのセッションのライフサイクル中にこのユーザーIDで始まるトピックに対する発行およびサブスクライブの権限を確保するだけで済みます。
MQTTリクエスト/レスポンス使用の提案
以下は、MQTTでリクエスト/レスポンスを使用する際の提案です。これらに従うことで、ベストプラクティスを実装するのに役立ちます:
- MQTTのQoS 1および2はメッセージがサーバーに到達することを保証しますが、メッセージがサブスクライバーに到達したかどうかを確認したい場合は、リクエスト/レスポンスパターンを使用できます。
- リクエストを送信する前にレスポンストピックにサブスクライブして、レスポンスを見逃さないようにします。
- レスポンストピックに対する発行およびサブスクライブの権限をレスポンダーとリクエスターの両方が持っていることを確認します。レスポンス情報は、権限要件を満たすレスポンストピックを構築するのに役立ちます。
- 複数のリクエスターが存在する場合、レスポンスの混乱を避けるために異なるレスポンストピックを使用する必要があります。クライアントIDをトピックの一部として使用することが一般的な方法です。
- 複数のレスポンダーが存在する場合、リクエスターがリクエストにコリレーションデータを設定することで、レスポンスの混乱を避けることが最善です。
- リクエスト/レスポンスとウィルメッセージを連携させることができます。接続時にウィルメッセージのレスポンストピックを設定するだけで、クライアントがオフライン期間中にウィルメッセージが消費されたかどうかを知ることができ、適切な調整を行うことができます。
デモ
次に、MQTTXを使用して、スマートフォンから寝室のライトをリモートで操作し、レスポンスを受け取るシナリオをシミュレートします。
MQTTXをインストールして開きます。新しいクライアント接続を作成し、モバイルフォンをシミュレートします。MQTTバージョンを5.0に設定し、レスポンストピックとして
state/light-in-bedroom/power
にサブスクライブします:スマートライトをシミュレートするために新しいクライアント接続を作成し、リクエストトピック
cmnd/light-in-bedroom/power
にサブスクライブします:リクエストクライアントに戻り、レスポンストピックを指定してリクエストトピック
cmnd/light-in-bedroom/power
にライトをオンにするコマンドを送信します:レスポンスクライアントでは、受信したメッセージがレスポンストピックを含んでいることが確認できます。次に、このレスポンストピックに基づいてライトをオンにする操作を実行し、レスポンストピックを通じて最新のライトの状態を返します:
最終的に、リクエストクライアントはこのレスポンスを受信し、レスポンスメッセージの内容に基づいてライトが正常にオンになったことを確認できます。
これは非常にシンプルな例ですが、パブリッシャーやレスポンダーの数を増やして、これらの場合にリクエストおよびレスポンストピックをどのように設計するかを体験することもできます。
さらに、リクエスト/レスポンスのPythonサンプルコードをemqx/MQTT-Features-Exampleで提供していますので、参考にしてください。