MQTT
MQTT ・ えむきゅーてぃーてぃー
非力な機器と切れやすい回線のために作られた、ブローカー仲介の軽量なpub/sub型メッセージングプロトコル。
✦ 30秒で試す — 航海切れやすい回線のために、届け方の約束を3段階に分けたブローカーから先で回線が切れたら、温度は?予想してみる概要
MQTT(MQ Telemetry Transport)は、センサーや家電のような非力な機器が、細い・不安定な回線ごしにメッセージをやり取りするために設計された、軽量な pub/sub(出版/購読)型のメッセージングプロトコルです。固定ヘッダはわずか 2 バイトからで、電池駆動のマイコンでも実装・維持できる小ささが持ち味です。
登場するのは IoT(モノのインターネット)の現場が中心です。温度センサーが測定値を送る、スマートロックに施錠指示を届ける、工場の設備が稼働状況を報告する — こうした「小さなメッセージを大量の機器と細く長くやり取りする」場面で、MQTT は事実上の標準になっています。AWS IoT Core や Azure IoT Hub といったクラウドの IoT 基盤も、機器との会話には MQTT を採用しています。
なぜ生まれたか
MQTT の原型は 1999 年、石油パイプラインの監視のために IBM のエンジニアらが設計しました。砂漠に点在するセンサーは衛星回線で通信しており、回線は細く、従量課金で、頻繁に切れる。そんな環境で HTTP のようなプロトコルを使うと、リクエストのたびに長いヘッダが付き、接続の確立と切断を繰り返し、貴重な帯域と電力を食い潰してしまいます。また HTTP は「クライアントが尋ねてサーバが答える」形なので、サーバ側から機器へ指示を押し届けるのも不得手でした。
そこで、(1) ヘッダを極限まで削る、(2) TCP 接続を張りっぱなしにして双方向にメッセージを押し流す、(3) 送り手と受け手を直接つながず中間のブローカーで仲介する、という設計が選ばれました。特に (3) が重要で、いつ切れるかわからない何万台もの機器同士が互いの生死やアドレスを気にし合うのは不可能です。全員が 1 か所のブローカーとだけ会話すればよい、という pub/sub 構造が、不安定な大量接続の問題を一挙に解きました。その後 IBM が仕様を公開し、2014年に OASIS の標準(MQTT 3.1.1)、2019年には理由コードや共有購読を加えた MQTT 5 が標準になっています。
詳細
ブローカーを中心とした pub/sub 構造
MQTT の登場人物は「ブローカー」と「クライアント」の 2 種類だけです。ブローカーはメッセージの集配所となるサーバ(Mosquitto や EMQX が代表的な実装)で、すべてのクライアントはブローカーに接続します。メッセージを送る側(publisher)は「トピック」という宛名を付けて publish し、受け取る側(subscriber)は関心のあるトピックを事前に subscribe しておくと、該当メッセージがブローカーから配送されてきます。送り手は「誰が受け取るか」を知らず、受け手は「誰が送ったか」を知らなくてよい — この疎結合(結合度と凝集度でいう結合の弱さ)が pub/sub の核心です。
トピック階層とワイルドカード
トピックは home/livingroom/temperature のようにスラッシュ区切りの階層で表現します。購読側はワイルドカードが使え、+ は 1 階層任意(home/+/temperature で全部屋の温度)、# は以降すべて(home/# で家中の全メッセージ)にマッチします。トピックは事前登録不要で、publish した瞬間に存在し始めます。この身軽さは強力ですが、裏を返せば命名規約を決めないとトピック空間がすぐ無法地帯になるため、実務では「場所/機器種別/計測項目」のような階層設計を最初に固めるのが定石です。
QoS — 3 段階の配達保証
MQTT は TCP の上で動くので、つながっている間のパケットの欠落は TCP が再送で埋めてくれます。問題は、回線そのものが切れたときです。切れた瞬間に飛んでいたメッセージは、届いたのか、届いたが受領確認だけが失われたのか、送り手には分かりません。そこで MQTT はメッセージごとに配達保証のレベル(QoS)を選べるようにし、再接続したあとに「確認をもらっていないもの」を送り直す決まりを置いています(MQTT 5 では、送り直しは再接続のときだけと定められています)。
QoS 0 は「最大1回」です。PUBLISH を1通送るだけで受領確認を待たず、失われても誰も送り直しません。毎秒流れるセンサーの値のように「次の値がすぐ来るから1個欠けても困らない」データ向きです。QoS 1 は「少なくとも1回」です。受け手は PUBACK を返し、送り手は PUBACK をもらうまでメッセージを手元に残して、再接続したら送り直します。欠けはしませんが、PUBACK のほうが失われると、届いていたメッセージをもう一度送るので重複が起きます。受け手には、同じメッセージを2回処理しても結果が変わらない冪等性が求められます。
QoS 2 は「ちょうど1回」です。PUBLISH → PUBREC → PUBREL → PUBCOMP の4通、つまり2往復で1件を運びます。受け手は PUBLISH を受け取るとそのパケット ID を記録し、同じ ID の PUBLISH が送り直されてきても2回目は渡さずに PUBREC だけ返します。送り手が PUBREL(「もう送り直さないので ID を忘れてよい」という合図)を送り、受け手が PUBCOMP で応えたところで、記録した ID を消します。重複も欠落も防げる代わりに、往復が倍になって遅く重くなるので、課金のように「二重に処理すると事故になる」メッセージに限って使うのが普通です。
気をつけたいのは、QoS が区間ごとの約束だということです。センサーからブローカーまでは発行するときの QoS、ブローカーから購読者までは「発行の QoS と購読するときに指定した QoS の低いほう」で運ばれます。センサーが QoS 2 で送っても、ダッシュボードが QoS 0 で購読していれば、ブローカーから先は投げっぱなしです。
下の部品で確かめてください。「温度を発行」を押すとメッセージが2区間を運ばれます。飛んでいるパケットをクリックすると回線が切れ、そのとき飛んでいたパケットが失われます。QoS を切り替えながら、PUBLISH を落としたとき、受領確認を落としたときに、ダッシュボードに何が届くかを比べてください。
「温度を発行」を押すと、センサーからブローカー経由でダッシュボードへ値が運ばれます。飛んでいるパケットをクリックすると回線が切れ、そのパケットが失われます。まずは QoS 0 のまま、PUBLISH を落としてみてください。
遺言と保持 — 不安定さを前提にした道具立て
MQTT には、切断が日常であることを前提にした気の利いた機能が 2 つあります。ひとつが Last Will(遺言)です。クライアントは接続時に「自分が突然いなくなったら、このトピックにこのメッセージを流してほしい」とブローカーに預けておけます。電池切れや圏外で機器が黙って消えても、ブローカーが代わりに「offline」を配送してくれるため、購読側は機器の生死を把握できます。もうひとつが Retained Message(保持メッセージ)で、トピックごとに最後の 1 通をブローカーが記憶し、新しく購読を始めたクライアントへ即座に渡します。これがないと、1 時間に 1 回しか値を送らないセンサーの現在値を知るために最大 1 時間待つことになります。
メッセージキューとの違い、WebSocket との併用
pub/sub と聞くとメッセージキュー(RabbitMQ や Kafka)を思い浮かべるかもしれません。送り手と受け手を仲介者で疎結合にする発想は共通ですが、力点が違います。メッセージキューはサーバ間連携のための基盤で、メッセージの蓄積・再読・分散処理に強く、その分リッチで重い。MQTT は「非力な端末との細い接続を大量に維持する」ことに特化し、蓄積機能は最小限(保持メッセージと、切断中の購読者向けの一時保存程度)です。実務では「機器からブローカーまでは MQTT、ブローカーから先の業務処理は Kafka などへ流し込む」という組み合わせが定番です。
また、ブラウザは生の TCP のソケットを開けないため、Web ページから MQTT を話すときは WebSocket の上に MQTT を載せるトンネリングを使います。主要ブローカーはこの口を標準で備えており、ダッシュボードのブラウザ画面がセンサー値をリアルタイム受信する、といった構成が手軽に作れます。なお本番運用では TLS による暗号化(ポート 8883)と、ユーザー名・パスワードやクライアント証明書による認証がほぼ必須です。ポート 1883(平文)を公開したまま放置されたブローカーが世界中から覗かれる、というのは IoT セキュリティの定番の事故例です。
試問
センサーの温度が、ブローカーを経てダッシュボードへ2つの区間を運ばれます。上のボタンで「発行の QoS」(センサーが送るときの約束)と「購読の QoS」(ダッシュボードが受け取るときの約束)を選び、「温度を発行」で送ります。飛んでいるパケットをクリックすると回線が切れて、そのパケットが失われます(再接続すると、確認をもらっていない送り手は送り直します)。
「温度を発行」を押すと、センサーからブローカー経由でダッシュボードへ値が運ばれます。飛んでいるパケットをクリックすると回線が切れ、そのパケットが失われます。まずは QoS 0 のまま、PUBLISH を落としてみてください。
