天天看點

表哥問我物聯網協定——MQTT

微信關注公衆号【一隻故事】,一起聊聊物聯網,聊聊Java後端技術,聊聊你的故事

表哥,今天我們來聊聊 MQTT 協定吧。

已端好小闆凳,前排圍觀

我們将從以下幾個方面進行講解:

文章目錄

    • 為什麼是 MQTT
    • MQTT通訊模型
    • MQTT主題
    • MQTT控制封包
        • 固定封包頭
        • 可變封包頭
        • 有效負載
    • 消息服務品質
    • MQTT中一些比較重要的名詞概念

為什麼是 MQTT

MQTT 是目前使用最為廣泛的物聯網通訊協定,目前已占據了物聯網通訊協定的半壁江山。

各大廠物聯網開發平台都提供了對MQTT協定的支援,并占有很大比重。

那為什麼是 MQTT 了?不妨看看官方協定文檔給的摘要說明。

表哥問我物聯網協定——MQTT

簡單概括下來就是

  • 使用釋出/訂閱消息模式,提供了一對多的消息分發和應用之間的解耦。
  • 提供三種等級的消息服務品質:最多一次、至少一次和僅有一次。
  • 很小的傳輸消耗和協定資料交換,最大限度減少網絡流量
  • 異常連接配接斷開發生時,能通知到相關各方。

MQTT通訊模型

MQTT 是一個用戶端服務端架構的釋出/訂閱模式的消息傳輸協定。在 MQTT 通訊模型中包括 MQTT Broker 和 MQTT Client,消息流轉不是端到端的,而是 Client 與 Broker 之間的。

表哥問我物聯網協定——MQTT

消息訂閱者通過向Broker訂閱某個主題,消息釋出者發了個某個主題的消息,此時Broker會将此消息流轉到訂閱了該主題的訂閱者。

看到此處的小夥伴會發現這和主流的消息隊列,如rabbitmq、rocketmq的生産消費模型很像,其實不然,可以看看他們之間的對比。

傳統的消息中間件,例如消息隊列 MQ、消息隊列 Kafka 等都是面向微服務大資料等領域,負責消息的存儲和轉發,消息的生産者和消費者都是服務端應用。這種設計很适合服務端技術棧固定、語言平台固定的場景。而移動網際網路和 IoT 領域則有所不同,這類場景更側重于多語言多平台的海量裝置接入,消息的生産和消費過程的業務屬性很突出,傳統的消息中間件并不适合這些領域。

微消息隊列 MQTT 在設計上是一個面向移動網際網路和 IoT 領域的無狀态網關,隻關心海量移動端裝置的接入、管理和消息傳輸,消息資料的存儲則都會路由給後端存儲産品。

适用場景
MQTT 面向移動端場景,移動端場景一般都具備海量裝置,單裝置資料較少的特點。是以,微消息隊列 MQTT 适用于擁有大量線上用戶端(很多企業裝置端過萬,甚至上百萬),但每個用戶端消息較少的場景。
消息隊列 MQ 面向服務端的消息引擎,主要用于服務元件之間的解耦、異步通知、削峰填谷等,伺服器規模較小(極少企業伺服器規模過萬),但需要大量的消息處理,吞吐量要求高。是以,消息隊列 MQ 适用于服務端進行大批量的資料處理和分析的場景。

更多詳細對比,讀者可以參考阿裡雲物聯網開發平台介紹:

https://help.aliyun.com/document_detail/94521.html

MQTT主題

上面就提到過主題,主題是消息流轉的媒介。訂閱和釋出必須要有主題,隻有當訂閱了某個主題之後,隻能收到相應主題發送過來的消息,以此來達到裝置和雲端的通訊。一個 MQTT Client 可以同時訂閱多個主題,同一個主題也可以被多個 MQTT Client 訂閱。

主題層級分隔符

斜杠用于分割主題的每個層級,為主題提供一個分層結構。

單層通配符

加号

+

用于單個主題層級比對的通配符。在主題過濾器的任意層級都可以使用單層通配符,包括第一個和最後一個層次。然而它必須占據過濾器的整個層級。也可以在主題過濾器的多個層級使用它。

例如

device/+

的主題可以比對

/device/1

/device/2

,但是不能比對

/device/1/1

多層通配符

#

号用于比對層級中任何層級的通配符。多層通配符表示它的父級和任意數量的自層級。多層通配符必須位于它自己的層級或者跟在主題層級分隔符的後面。不管哪種情況,它都必須是主題過濾器的最後一個字元。

例如

device/#

的主題都可以比對

/device/1

/device/2

/device/1/1

的主題消息。

$ 号開頭的主題

服務端不能将

$

字元開頭的主題名比對通配符(

#

+

)開頭的主題過濾器。服務端應該阻止用戶端使用這種主題名與其它用戶端交換消息。服務端實作可以将

$

開頭的主題名用作其他目的。例如我們後面講到的 MQTT 服務端 EMQX 就使用

$SYS

作為系統主題,用戶端訂閱相應的系統主題可以感覺裝置上下線狀态,服務端運作資料等等

MQTT控制封包

MQTT Client 和 Broker 之間通過交換 MQTT 控制封包來通信。控制封包有很多,MQTT Client 向 MQTT Broker 發送連接配接封包,釋出訂閱主題消息封包等等。這一節描述這些封包的格式。 MQTT 控制封包格式部分組成:

  1. 固定封包頭,所有控制封包都包含
  2. 可變封包頭,部分控制封包包含
  3. 有效負載 Payload,部分控制封包包含

固定封包頭

Bit 7 6 5 4 3 2 1
位元組 1 MQTT 資料包類型 用于指定控制封包類型的标志位
從第二個位元組開始 剩餘長度

固定封包頭的高 4 位用于指定資料包的類型:

名字 封包流動方向 描述
Reserved 禁止 保留
CONNECT 1 用戶端到服務端 用戶端請求連接配接伺服器
CONNACK 2 服務端到用戶端 連接配接封包确認
PUBLISH 3 兩個方向都允許 用戶端向伺服器發送消息
PUBACK 4 兩個方向都允許 Qos1消息釋出收到确認
PUBREC 5 兩個方向都允許 釋出收到(保證傳遞第一步)
PUBREL 6 兩個方向都允許 釋出釋放(保證傳遞第二步)
PUBCOMP 7 兩個方向都允許 Qos2 消息釋出完成(保證消息第三步)
SUBSCRIBE 8 用戶端到服務端 用戶端請求訂閱
SUBACK 9 服務端到用戶端 訂閱請求封包确認
UNSUBSCRIBE 10 用戶端到服務端 用戶端取消訂閱請求
UNSUBACK 11 服務端到用戶端 取消訂閱封包确認
PINGREQ 12 用戶端到服務端 心跳請求
PINGRESP 13 服務端到用戶端 心跳響應
DISCONNECT 14 用戶端到服務端 用戶端斷開連接配接
Reserved 15 禁止 保留

固定封包頭的低四位包含每個 MQTT 控制封包類型特定的辨別。表格中的“保留”的标志位,都是保留給以後使用,這裡不用過多在意。

控制封包 固定封包辨別 Bit 3 Bit2 Bit1 Bit0
CONNECT 保留
CONNACK 保留
PUBLISH 用于 MQTT 3.1.1 DUP QoS QoS RETAIN
PUBACK 保留
PUBREC 保留
PUBREL 保留 1
PUBCOMP 保留
SUBSCRIBE 保留 1
SUBACK 保留
UNSUBSCRIBE 保留 1
UNSUBACK 保留
PINGREQ 保留
PINGRESP 保留
DISCONNECT 保留
  • DUP:控制封包的重複分發辨別
  • QoS:有兩位,PUBLISH 封包服務品質等級(0、1、2)
  • RETAIN :PUBLISH 封包保留辨別

介紹 MQTT 特點的時候,我們提到過 MQTT Client 向 Broker 發送消息的時候可以指定消息品質:最多一次,最少一次和隻有一次。至少一次肯定就意味着消息可能會重複發送,DUP 就用于辨別是否是重複的封包,消息重發在 MQTT Client 庫中已經預設你實作。RETAIN 保留消息,後面會詳細講到。

剩餘長度

從第二位元組開始,表示剩餘長度。剩餘長度用于辨別可變封包頭和負載資料的總的位元組數,不包含其本身的位元組數。

剩餘長度使用一個變長的編碼方案,最小是一個位元組,最大是 4 個位元組。每一個位元組的最高位辨別後面一個位元組也是剩餘長度辨別,低 7 位有效位用于編碼資料。是以按照最大是 4 個位元組算,允許發送 (0xFF, 0xFF, 0xFF, 0x7F)大小的控制封包,約 256M。

是以一個 MQTT 控制封包最大傳輸的資料約為 256M。

位元組數 最小值 最大值
1 0 (0x00) 127 (0x7F)
2 128 (0x80, 0x01) 16 383 (0xFF, 0x7F)
3 16 384 (0x80, 0x80, 0x01) 2 097 151 (0xFF, 0xFF, 0x7F)
4 2 097 152 (0x80, 0x80, 0x80, 0x01) 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F)

可變封包頭

某些 MQTT 控制封包包含一個可變報頭部分。它在固定報頭和負載之間。可變報頭的内容根據封包類型的不同而不同。可變報頭的封包辨別符(Packet Identifier)字段存在于在多個類型的封包裡。

封包辨別符 Packet Identifier

封包辨別符隻存在某些特定的控制封包中,如 SUBSCRIBE,UNSUBSCRIBE 和 PUBLISH(QoS 大于 0),用于辨別該封包的唯一性。用戶端向伺服器發送這些唯一性的封包,伺服器相應的,會帶上該封包辨別回複 ACK 給用戶端。另外的對于 PUBLISH(QoS 大于 0)的封包,當消息重發的時候,封包辨別符也不會改變。

用戶端和服務端彼此獨立地配置設定封包辨別符。是以,用戶端服務端組合使用相同的封包辨別符可以實作并發的消息交換。

有效負載

某些 MQTT 控制封包在封包的最後部分包含一個有效載荷。對于 PUBLISH 來說有效載荷就是應用消息。

每個類型封包具體格式可以看看 MQTT 官方協定文檔。協定文檔可以公衆号回複 [MQTT] 關鍵字領取

消息服務品質

MQTT 協定中規定了消息服務品質,它保證了在不同的網絡環境下消息傳遞的可靠性,QoS 的設計是 MQTT 協定裡的重點。作為專為物聯網場景設計的協定,MQTT 的運作場景不僅僅是 PC,而是更廣泛的窄帶寬網絡和低功耗裝置,如果能在協定層解決傳輸品質的問題,将為物聯網應用的開發提供極大便利。

MQTT 釋出消息不是端到端的,是用戶端與伺服器之間的。訂閱者收到 MQTT 消息的 QoS 級别,最終取決于釋出消息的 QoS 和主題訂閱的 QoS,準确來說是取兩者的最小值。

釋出消息的 QoS 主題訂閱的 QoS 接受消息的 QoS
1
2
1
1 1 1
1 2 1
2
2 1 1
2 2 2

QoS0:最多分發一次

當 QoS 為 0 時,消息的分發依賴于底層網絡的能力。釋出者隻會釋出一次消息,接收者不會應答消息,釋出者也不會儲存和重發消息。消息在這個等級下具有最高的傳輸效率,但可能送達一次也可能根本沒送達。

這種情況下,如果開發人員想要保證消息的穩定送達,必須在業務上層設計一套消息重發機制。

表哥問我物聯網協定——MQTT

QoS1:至少分發一次

當 QoS 為 1 時,可以保證消息至少送達一次。MQTT 通過簡單的 ACK 機制來保證 QoS 1。釋出者會釋出消息,并等待接收者的 PUBACK 封包的應答,如果在規定的時間内沒有收到 PUBACK 的應答,釋出者會将消息的 DUP 置為 1 并重發消息。接收者接收到 QoS 為 1 的消息時應該回應 PUBACK 封包,接收者可能會多次接受同一個消息,無論 DUP 标志如何,接收者都會将收到的消息當作一個新的消息并發送 PUBACK 封包應答。

表哥問我物聯網協定——MQTT

QoS2: 僅分發一次

當 QoS 為 2 時,釋出者和訂閱者通過兩次會話來保證消息隻被傳遞一次,這是最高等級的服務品質,消息丢失和重複都是不可接受的。使用這個服務品質等級會有額外的開銷。

釋出者釋出 QoS 為 2 的消息之後,會将釋出的消息儲存起來并等待接收者回複 PUBREC 的消息,發送者收到 PUBREC 消息後,它就可以安全丢棄掉之前的釋出消息,因為它已經知道接收者成功收到了消息。釋出者會儲存 PUBREC 消息并應答一個 PUBREL,等待接收者回複 PUBCOMP 消息,當發送者收到 PUBCOMP 消息之後會清空之前所儲存的狀态。

當接收者接收到一條 QoS 為 2 的 PUBLISH 消息時,他會處理此消息并傳回一條 PUBREC 進行應答。當接收者收到 PUBREL 消息之後,它會丢棄掉所有已儲存的狀态,并回複 PUBCOMP。

無論在傳輸過程中何時出現丢包,發送端都負責重發上一條消息。不管發送端是 Publisher 還是 Broker,都是如此。是以,接收端也需要對每一條指令消息都進行應答。

表哥問我物聯網協定——MQTT

如何選取 QoS

QoS 級别越高,流程越複雜,系統資源消耗越大。應用程式可以根據自己的網絡場景和業務需求,選擇合适的 QoS 級别,比如在同一個子網内部的服務間的消息互動往往選用 QoS 0;而通過網際網路的實時消息通信往往選用 QoS 1;QoS 2 使用的場景相對少一些,适合一些支付請求之類的要求較高的場景。

MQTT中一些比較重要的名詞概念

保留标志 RETAIN

在MQTT PUBLISH封包中設定釋出的消息是否為保留消息,如果為 true,那麼消息會一直駐留在 Broker 中,新上線的 Client 如果訂閱了該主題的消息,那麼就會收到該保留消息。這個功能使用的場景也非常多。

保留消息越來越多,勢必會對伺服器造成資源消耗,清除不必要的保留消息有兩種方式:

  • 向該主題發送一個空的消息
  • Broker 設定保留消息的逾時時間

保持時間 Keep Alive

在MQTT CONNECT封包中可以設定Keep Alive時間。保活時間指在用戶端傳輸完成一個控制封包的時刻到發送下一個封包的時刻,兩者之間允許空閑的最大時間間隔。如果沒有任何其它的控制封包可以發送,用戶端必須發送一個 PINGREQ 封包(MQTT Client 包會幫我們定時發送心跳封包),否則 MQTT Broker 會強制關閉和 Client 之間的連接配接。這種情況經常出現在嵌入式裝置網絡不穩定的時候。

清除會話 Clean Session

在MQTT CONNECT封包中可以設定Clean Session 。當用戶端指定持久會話時,也就是Clean Session設定為false,即時用戶端斷線,斷線期間 Broker 會保留其他用戶端向該用戶端訂閱的主題上發送的消息。當用戶端重新上線後,會接收到離線期間的消息。

遺願消息 Last Will

在MQTT CONNECT封包中可以設定遺願主題和遺願消息。

用戶端連接配接伺服器如果指定遺願消息,那麼該主題的遺願消息會駐留在 MQTT Broker 中。

特别注意的是,當 MQTT 用戶端異常下線時(用戶端斷開前未向伺服器發送 DISCONNECT 消息,Broker通過心跳檢測到用戶端已下線),MQTT 消息伺服器才會釋出遺願消息。MQTT 主動通過 DISCONNECT 封包斷開與伺服器連接配接時,則不會發送遺願消息。

以上就是MQTT協定的核心内容,其實協定簡單,關鍵是協定的實作者,需要在了解協定的基礎之上去編碼和大量的測試。表哥,你還有啥問題嗎?

我看MQTT client已經有非常多語言庫了,MQTT Broker你要啥好的推薦的嗎?

給你一張圖自己去領悟吧。

表哥問我物聯網協定——MQTT

是目前比較火的一個MQTT Broker。表哥你不是netty玩的6嗎,可以自己去實作一個,可以參考JMQTT的設計,JMQTT叢集也有解決方案。

好的,我這就去了解了解