1. 概述
MQTT(Message Queuing Telemetry Transport,訊息佇列遙測傳輸協議),是一種基於釋出/訂閱(publish/subscribe)模式的"輕量級"通訊協議,該協議構建於TCP/IP協議上。
基本原理如下圖所示
MQTT協議已經成為物聯網數據傳輸的標準,具備以下優勢:
輕量、高效:IoT裝置上的MQTT實施需要最少的資源,最小的MQTT控制訊息可以少至2個數據位元組,MQTT訊息的頭也很小,可以最佳化網路頻寬。
可擴充套件:MQTT實施最要最少的程式碼:在操作中消耗的功率非常少。該協議還具有支援與大量物聯網裝置通訊的內建功能。
可靠:許多IoT裝置透過低頻寬、高延遲的不可靠網路,MQTT具有內建功能,可減少IoT裝置重新連線雲所需時間,還定義了三種不同服務級別,確保IoT用例的可靠性。
安全:可以輕鬆使用現代身份驗證協議(OAuth、TLS1.3)加密訊息,並對裝置和使用者進行身份認證。
得到良好的支援:多種語言對MQTT協議的實施,提供廣泛的支援。
2. 協議格式
MQTT報文由三部分組成:
Fiexd header固定報頭,所有控制報文都包含
Variable header可變報頭,部分控制報文包含
Payload有效載荷,部分控制報文包含
在控制報文傳輸中,每一個字串都有一個兩位元組的長度(用了大端序序,高位元組在低位元組前面。高位元組用MSB表示,低位元組用LSB表示)欄位作為字首,這也意味著,控制報文中的字串(注意不是有效載荷裡的內容),長度最大隻能到65535位元組。
固定報頭
第一個位元組內容:控制報文型別、控制報文型別標誌位
第二個位元組內容:剩餘長度,表示當前報文剩餘部分的位元組數,包括可變報頭和負載的資料。但是一個位元組只能表示255的長度,所以這裏設計的是可變位元組長度,用來表示不同長度。最大可以傳輸268435422個位元組內容
位元組數 | 最小值 | 最大值 |
---|---|---|
1 | 0(00000000) | 127(011111111) |
2 | 128(10000000,00000001) | 16383(11111111,01111111) |
3 | 16384(10000000,10000000,00000001) | 2097151(11111111,11111111,01111111) |
4 | 2097152(10000000,10000000,10000000,00000001) | 268435422(11111111,11111111,11111111,01111111) |
3. 控制報文
3.1 CONNECT
客戶端到服務端的網路建立之後,客戶端傳送給服務端的第一個報文必須是Connect報文。在同一個TCP連線上,客戶端只能傳送一次Connect報文。
3.1.1 固定報文
第一個位元組:報文型別為1,無標誌位
3.1.2 可變報文
可變報文由四個欄位,共10位元組組成,按位元組順序:協議名(6位元組)、協議級別(1位元組)、連線標誌(1位元組)、保持連線(2位元組)
協議名:前兩個位元組為協議名稱長度,值為4,後四個位元組為“MQTT”字串。
協議級別:對於MQTT 3.1.1協議,值為4,如果是MQTT5.0協議,值為5。
3.1.2.1 連線標誌
連線標誌位比較多,具體如下所示。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
User Name Flag | Password Flag | Will Retain | Will QoS | Will QoS | Will Flag | Clean Sesson | Reserved | |
byte 8 | X | X | X | X | X | X | X | 0 |
清理會話標誌
Clean Session設定為1,客戶端和服務端必須丟棄之前任何會話,並且開始一個新的會話。
Clean Session設定為0,服務端必須基於當前會話(使用客戶端識別符號識別)的狀態,恢復與客戶端通訊。如果沒有歷史會話,則建立一個新的會話。在連線斷開後,客戶端和服務端必須儲存會話資訊,服務端還要將之後的QoS1和QoS2級別的訊息,儲存為會話狀態的一部分(如果這些訊息匹配斷開連線客戶端的訂閱Topic)。
遺囑標誌
Will Flag被設定為1,表示如果連線請求被接受了,遺囑訊息(Will Message)必須被儲存在服務端,並且與這個客戶端網路關聯。如果這個客戶端網路連線關閉時,服務端必須釋出這個遺囑訊息,除非客戶端在DISCONNECT報文時刪除了遺囑訊息。
Will Flag被設定為1,Will QoS和Will Retain欄位會用到,同時有效載荷中必須包含Will Topic和Will Message。
遺囑QoS標誌
用於指定釋出遺囑訊息時使用的服務質量等級,如果Will Flag為0,Will QoS也必須為0
遺囑保留標誌
Retain訊息=保留訊息
首先理解保留訊息:如果客戶端傳送的訊息中設定了保留標誌,那麼服務端必須儲存這個訊息和對應的服務等級,以便它可以傳送給後來訂閱的客戶端。
一個topic只能有1條保留訊息,新的保留訊息會覆蓋舊的。當傳送保留訊息是,載荷內容為空,會刪除保留訊息。
如果遺囑保留標誌設定為1,服務端必須將遺囑訊息當做保留訊息釋出。也就是說,如果這個客戶端斷開時,會發送遺囑訊息,同時訊息也會保留起來,讓後續新的客戶端定於這個遺囑topic時,也能收到這個遺囑訊息。
使用者名稱標誌
如果使用者名稱標誌被設定為1,那麼有效載荷必須包含使用者名稱欄位
密碼標誌
如果密碼標誌被設定為1,那麼有效載荷必須包含密碼欄位。如果使用者標誌被設定為0,密碼標誌也必須設定為0
3.1.2.2 保持連線
連線保持:以秒為單位的時間間隔,必須要在規定時間內傳送控制報文,如果沒有可以傳送PINGREQ,同時服務端在1.5倍保持連線時間內沒有收到客戶端的控制報文,需要斷開客戶端的網路連線。因為保持連線的只有2位元組儲存,所以理論上最大的就是65535秒,也就是18小時12分。(不過實際應該不會設定這麼長的時間,因為中間路由可能有過期時間)
3.1.3有效載荷
可變報文中的標誌,決定是否包含這些欄位,如果包含,必須按照這個順序出現:客戶端識別符號、遺囑主題,遺囑訊息,使用者名稱,密碼。
客戶端識別符號(ClientId)必須存在,而且必須是Coneect報文的第一個欄位。不過,可以允許客戶端提供一個0位元組的ClientId,服務端需要分配唯一的客戶端識別符號給客戶端。如果客戶端提供了0位元組的clientId,必須同時建清理會話標誌設定為1。(因為會話恢復時根據clientId的)
3.2 PUBLISH
客戶端向服務端,或者服務端向客戶端傳送的一個訊息
3.2.1 固定報頭
第一個位元組:報文型別為3,有三個標誌位:DUP、QoS等級、RETAIN。
重發標誌
DUP設定為0,表示這是客戶端或者服務端第一次傳送這個報文。DUP被設定為1,表示這可能是一個之前傳送過的報文。對於QoS為0的訊息,DUP標誌必須設定為0。
服務質量等級
QoS為0,最多分發一次。
QoS為1,至少分發一次。
QoS為2,只分發一次
保留標誌
如果客戶端傳送的訊息中設定了保留標誌,那麼服務端必須儲存這個訊息和對應的服務等級,以便它可以傳送給後來訂閱的客戶端。
一個topic只能有1條保留訊息,新的保留訊息會覆蓋舊的。當傳送保留訊息時,載荷內容為空,表示刪除保留訊息。
3.2.2可變報頭
主題名:主題名必須是PUBLISH報文中可變報頭的第一個欄位。
報文識別符號:當QoS等級是1或2時,報文識別符號欄位才能出現在PUBLISH報文中。
4. 會話
在物聯網場景中,裝置可能因為網路問題或者電源問題頻繁斷開連線。如果客戶端和服務端總是以全新的上下文建立連線,那麼會有以下問題:
客戶端在重連後必須重新訂閱主題才能繼續接收訊息,這會給伺服器帶來額外的開銷。
客戶端將會錯過離線期間的訊息。
QoS1和QoS2的服務質量將無法得到保證。
為此,MQTT協議設計了會話機制。MQTT會話本質上就是,一組需要服務端和客戶端額外儲存的上下文資料,這些資料可以僅持續與網路連線一樣長的時間,也可以跨越多個連續的網路連線存在。
服務端使用ClientID來唯一標識每個會話,如果客戶端想要在連線時複用之前的會話,那麼必須使用與此前一致的ClientID。
在MQTT3.1.1版本中,有Clean Session的會話機制。而在MQTT5.0則升級為Clean Start和Session Expiry Interval更多靈活的機制。
5.QoS
QoS關注的是單個傳送者到單個接受者的應用訊息。
5.1 QoS0:最多分發一次
問題:訊息可能會丟失,因為訊息的可靠性完全依賴底層的TCP協議,而TCP連線出現關閉、重置,就會出現訊息丟失。
訊息的分發依賴底層網路的能力。接受者不會發送訊息響應,傳送者也不會重試。訊息可能送達一次,也可能沒傳送達。
5.2 QoS1:至少分發一次
問題:訊息不會丟失,但是可能會重複。
重複的原因:
對於傳送方來說:
PUBLISH未到達接收方
PUBLISH已到達接收方,接受方的PUBACK報文未到達傳送方。
雖然重傳是PUBLISH報文中的DUP標誌被設定為1,用於表示是一個重傳報文,但是接收方無法確認,主要接收方有兩種情況:
傳送方確實因為沒有收到PUBACK而重傳PUBLISH報文,接收方收到相同PacketID,並且第二個PUBLISH報文DUP為1,確實是重複訊息。
第一個PUBLISH訊息完成投遞,PacketID可用。傳送方使用這個PacketID傳送全新的PUBLISH報文,但是第一次沒有傳送到對端,又重新發了PUBLISH報文,此時是相同的PacketID,並且DUP為1,確實是個全新的訊息。
接收方無法區分這兩種情況,只能將PUBLISH報文都當作全新的報文處理,因為使用QoS1時,訊息的重複在協議層面無法避免。
QoS1確保訊息至少傳送一次。QoS1的PUBLISH報文的可變報頭中包含一個未使用的報文識別符號(msgId),需要PUBACK確認。
沒有要求要在傳送PUBACK之前,把訊息分發完。當傳送者收到PUBACK時,報文識別符號可以複用。
5.3 QoS2:僅分發一次
訊息丟失和重複都不可接受。那麼是如何保證不會重複呢?關鍵在於通訊雙發如何正確地同步釋放PacketID。不論是重傳訊息還是釋出新訊息,一定是和對端達成了共識。
根據QoS2規定:一旦傳送了對應的PUBREL報文後,就不能重發這個PUBLISH報文。收到PUBCOM報文後,這個報文識別符號就可以重用。
因此,對於接收方來說,能夠以PUBREL報文為界限,凡是在PUBREL報文之前到達的PUBLISH報文,都是重複的訊息。凡是在PUBREL之後達到的PUBLISH報文,都是全新的訊息。
QoS2可能的流程一:
QoS2可能的流程二:
6. Q&A
結合moqueue的實現,進一步理解MQTT協議的內容
保留訊息處理邏輯?
Broker會將訊息進行儲存起來,以topic的維度。正常不會清理,除非傳送者傳送了清理保留訊息。
遺囑訊息處理邏輯
在ChannelInactive(Netty監聽客戶端斷開),如果有遺囑訊息的話,會發送遺囑訊息邏輯。也就是把遺囑訊息傳送給對應的訂閱者。
遺囑保留訊息處理邏輯?
沒有看到處理,只有遺囑訊息的處理,在處理的時候沒有再判斷保留的情況。
哪些資料需要持久化?會話的資訊?伺服器宕機的話,怎麼處理持久化資訊?
保留訊息、訂閱訊息。在moquette的實現中,還可能會建立佇列,用來儲存暫時無法寫入的訊息(緩衝區滿了,視窗限流等),會在一些場景(緩衝區可寫,Session會話重開的是)下重新執行。會話是基於記憶體儲存的,伺服器宕機需要重新建立。
MQTT服務端怎麼做負載均衡?
協議上不支援,需要在MQTT叢集前引入一個負載均衡器,可以是HAProxy 2.4 和 Nginx Plus(Nginx的付費版本)支援。不然只能當成4層轉發進行處理。
QoS1/QoS2如何保障?是收到publish訊息就傳送其他客戶端,還是怎麼樣?
QoS1,在broker的實現,會先pulish訊息,然後在回覆ACK。
在QoS2的實現上,會先回復pubrec,然後會publish訊息,偏向於流程二。
實際QoS生效,不僅僅取決於傳送端,還取決於訂閱端。最終到訂閱的QoS為min(傳送端,訂閱端)。實際訊息傳送到訂閱端,就是broker傳送Publish訊息到訂閱段,此時broker是傳送端,訂閱端是接收方。
7. 參考連結
MQTT3.1.1協議
MQTT5.0 : github.com/hui6075/mqt…
MQTT中文網:www.mqtt.cn/822.html