MQTTとMessagePub+ :要求系トピック
要求系トピックはMQTTクライアントに対してなんらかの要求を送るためのトピックです。典型的なIoTシステムにおいては、デバイスの制御や、設定値の変更などに用いられます。アプリケーションサーバー(MQTTクライアント)同士の連携制御などにも利用されることがあります。
![6660_1.png](/column/images/6660_1.png)
トピック設計の主体
要求系トピックは、要求を受け付ける側を主体として設計します。例えば、スマートホームアプリが家電製品を制御する場合、要求系トピックは家電製品を主体として設計します。スマートホームアプリ主体で設計してしまうと、それ以外のサービス、例えば省電力アプリなどからも家電製品を制御する場合に、トピックが使いにくくなってしまうことがあるためです。
分割単位
要求系トピックは、要求を受信する単位で分割します。例えば、照明デバイスが2個あり、それぞれがブローカに接続しているとします。
![6660_2.png](/column/images/6660_2.png)
まずは、照明制御トピックが全てひとつにまとまっていたらどうなるか考えてみます。
![6660_3.png](/column/images/6660_3.png)
照明デバイス1も、照明デバイス2も、「照明制御」トピックを共有します。この場合、トピックの中身のメッセージに、対象の照明デバイスを含めることになります。
![6660_4.png](/column/images/6660_4.png)
実際に、照明デバイス1をONにする場合のメッセージの流れを見てみます。MQTTはPub/Subモデルであるため、全てのサブスクライバにメッセージが配送されます。よって、照明デバイス1に対する制御要求が照明デバイス2にも配送されてしまいます。照明デバイス1ではこれを受信して制御しますが、照明デバイス2では、メッセージの中身を確認し、自分宛でないことが分かると、これを無視します。
このように、非効率な通信となってしまいます。
では、トピックを要求を受信する単位で分割したらどうなるでしょうか。
![6660_5.png](/column/images/6660_5.png)
まず、トピックの中身に対象デバイス情報を含める必要がなくなるため、シンプルになります。
![6660_6.png](/column/images/6660_6.png)
それぞれの照明デバイスは、自分に対応するトピックをサブスクライブします。スマートホームアプリは、制御したいデバイスに対応するトピックに対してパブリッシュを行います。こうすることで、無駄な通信が発生することはなくなります。
では次に、ひとつのデバイスに対する制御の種類が複数あるケースを考えてみます。例えば「照明ON/OFF制御」と「明るさ制御」という2種類の制御があるとします。この場合、トピックを分割すべきでしょうか。
通知系トピックでは、意味のある情報のまとまりを単位として分割することを推奨しました。retain機能を効果的に活用できるというのが理由のひとつでした。要求系トピックににおいても、retain機能を活用するのであれば、「照明ON/OFF制御」と「明るさ制御」は別々のトピックとすべきです。しかし筆者の経験では、通知系トピックに比べ、要求系トピックではretain機能を有効に使うことが難しい場合が多いです。例えば、要求系トピックを用いた処理には順序性が必要な場合があります。例えば、モードを切り替えてから値を設定するといった2つのステップから構成されるような処理などです。retain機能はトピックの最新の値を記憶しておくだけなので、順序性のある処理には向きません。もし、スマートホームアプリにおいて、接続直後のデバイスを所定の状態にしたいのであれば、まず、デバイスの状態を通知系トピックを介して知り、その情報に基づき、要求系トピックを使って制御すれば良いでしょう。要求系トピックへのパブリッシュにはretainフラグを設定しません(retainフラグ:0)。
要求系トピックへのパブリッシュにretainフラグを設定しないのであれば、「照明ON/OFF制御」と「明るさ制御」は、「照明制御」にまとめた方がシンプルでしょう。メッセージの内容で、「照明ON/OFF制御」と「明るさ制御」を区別します。
では、要求系トピックはデバイス毎にひとつで良いのでしょうか。実は分割した方が良いケースがあります。それは、権限を分けたい時です。具体例を挙げると「照明制御」という通常の要求系トピックと、「照明メンテナンス制御」という診断機能に関する要求系トピックの分割です。「照明制御」はスマートホームなど多くのアプリからのパブリッシュを許可しますが、「照明メンテナンス制御」は照明器具ベンダー提供の診断アプリにだけパブリッシュを許可したいとします。実は、MQTT規格では権限に関するエラーコード(Reason Code)は準備されているのですが、どのような権限モデルを持つべきかは規定されていません。しかし、多くのMQTTブローカでは、MQTTクライアントとトピックの組み合わせで、パブリッシュ権限、サブスクライブ権限を設定できます。トピックを分割することでこれが実現できるのです。
![6660_7.png](/column/images/6660_7.png)
これまでの基準をまとめると、「要求系トピックは、要求する単位(デバイス単位)で分割する」「要求の種類では分割しない」「権限の単位では分割する」ということになります。
![6659_8.png](/column/images/6660_8.png)
IoTシステムには、IoTゲートウェイが組み込まれているケースがあります。
![6660_9.png](/column/images/6660_9.png)
照明デバイス1と照明デバイス2がそれぞれMQTTクライアントの場合(上)と、IoTゲートウェイを介してMQTTブローカに接続する場合(下)で、トピック分割の基準は変わるのでしょうか。これはIoTゲートウェイをどうとらえるかに依存します。ゲートウェイを単なる仲介者と考え、主体はあくまでもデバイスと考えるのであれば、「照明デバイス1制御」「照明デバイス2制御」と分割すべきでしょう。IoTゲートウェイを主体と考えるのであれば、デバイス1やデバイス2は、「ON/OFF制御」や「明るさ制御」同様、制御の種類として扱うことができるので「照明制御」とトピックをまとめることができます。ゲートウェイで扱えるデバイス数に制限があるケースもあり、ゲートウェイをアップグレードした場合にトピック設計に影響がでることを避けたい場合、デバイス主体で分割すると良いでしょう。
パブリッシュのタイミング
要求系トピックへのパブリッシュは、基本的に制御や処理を要求したい時に行います。もし、要求を受ける側のクライアントがメッセージ配送中に切断してしまったら、要求メッセージはどうなるのでしょうか。MQTTには切断が生じてもメッセージを再送する仕組みがあります。
まず、要求を受ける側のクライアントが、MQTTブローカに接続する際、CONNECTパケットに設定する再送関連の項目が2つあります。ひとつは、Clean Startフラグです。これを0に指定すると、接続前にセッションが残っていたら、それを継続し、必要に応じて再送処理が行われます。もうひとつは、Session Expiry Intervalプロパティです。これは32ビットの整数値で、クライアントが切断後に指定した値(秒)の間、MQTTブローカがセッションを維持することを意味します。なお、最大値の0xFFFFFFFFが指定された場合、セッションは無期限で維持されます。クライアントが接続した時、以前のセッションを継続したかどうかは、CONNACKパケットのSession Presentフラグで知ることができます。1ならセッションが継続されており、0ならセッションが継続されず、今回の接続が新規接続であることを意味します。
セッションを維持するとは、クライアント切断後も、サブスクライブの状態が維持され、切断中にサブスクライブしているトピックに対してメッセージがパブリッシュされた場合、それをMQTTブローカが保持しておくことを意味します。そして、クライアントがClean Startフラグ0で再接続してきた時に、そのメッセージをクライアントに配送します。さらに、クライアントとMQTTブローカ間で送りかけのメッセージがある場合、それを再送します。
再送されるのは、QoSが1または2のメッセージです。QoSは0,1,2の3レベルが指定されており、それぞれ、0: At most once (高々1回)、1: At least once (少なくとも1回)、2: Exactly once(正確に1回)となっています。QoSは、サブスクライブ時とパブリッシュ時にそれぞれ指定可能で、トピック毎に、サブスクライブ時のQoSとパブリッシュ時のQoSの値の小さい方に調整されます。
QoS1での再送を想定して要求系トピックのメッセージを設計する場合、要求を受けての処理は、べき等性があるようにすべきです。複数回再送されたとしても、べき等性があれば、期待通りの状態となります。例えば、エアコンの温度設定を考えてみます。現在の設定温度が25℃で1℃設定温度を上げたいとします。メッセージの内容を「1℃温度を上げる」にすると、2回再送されると設定温度が27℃になってしまいます。一方、「26℃にする」であれば、何回再送されても設定温度は26℃です。後者のようなメッセージ設計は、べき等性があります。
QoS2の場合は、メッセージは必ず1回届くという前提で設計することができます。ただしこれは、MQTTレイヤでのメッセージの伝送が、切断を跨いでも必ず1回届くというだけです。例えば、要求を受ける側にMQTTのメッセージが届いたとしても、そのアプリケーション処理や、IoTゲートウェイでのプロトコル変換などにより、制御対象までメッセージが必ず1回届くとは限らない点に注意が必要です。
![6660_10.png](/column/images/6660_10.png)
具体的にどのような対策があるのかについては、応答系トピックの説明で述べたいと思います。
メッセージの有効期限
メッセージの有効期限は、要求メッセージをパブリッシュする時に、Message Expiry Intervalプロパティを指定することで設定できます。要求系トピック宛のメッセージでは、主に、要求を受ける側がセッションを維持したまま切断しているメッセージをいつまで維持するかに影響します。要求の種類に応じて設定すると良いでしょう。設定しない場合はMQTTの規格上は無期限に維持されます。ただし、多くのMQTTブローカでは、仮に無期限維持されると規格上解釈されるケースでも、一定の期限を設けていることがあります。特にクラウドサービスで提供されるMQTTブローカでは、大抵期限があるので、サービスのドキュメントを確認しておきましょう。 MessagePub+では、broker起動時のオプション--redis-expire-sec によって、Message Expiry Intervalプロパティを指定しなかった場合の有効期限を設定することができます。また、--redis-expire-secの値は、Message Expiry Intervalプロパティの上限としても用いられるため、--redis-expire-secの値よりも大きなMessage Expiry Intervalプロパティが指定された場合でも、上限の値が設定されます。
MQTTとMessagePub+
※この記事に掲載されている内容、および製品仕様、所属情報(会社名・部署名)は公開当時のものです。予告なく変更される場合がありますので、あらかじめご了承ください。
関連サービス
-
IoTメッセージングプラットフォーム「MessagePub+」
MessagePub+ はオージス総研が開発したつなげることに特化した"IoTメッセージングプラットフォーム"です