MQTT QoS 0, 1, 2 介紹

EMQX發表於2023-01-12

什麼是 QoS

很多時候,使用 MQTT 協議的裝置都執行在網路受限的環境下,而只依靠底層的 TCP 傳輸協議,並不能完全保證訊息的可靠到達。因此,MQTT 提供了 QoS 機制,其核心是設計了多種訊息互動機制來提供不同的服務質量,來滿足使用者在各種場景下對訊息可靠性的要求。

MQTT 定義了三個 QoS 等級,分別為:

  • QoS 0,最多交付一次。
  • QoS 1,至少交付一次。
  • QoS 2,只交付一次。

其中,使用 QoS 0 可能丟失訊息,使用 QoS 1 可以保證收到訊息,但訊息可能重複,使用 QoS 2 可以保證訊息既不丟失也不重複。QoS 等級從低到高,不僅意味著訊息可靠性的提升,也意味著傳輸複雜程度的提升。

在一個完整的從釋出者到訂閱者的訊息投遞流程中,QoS 等級是由釋出者在 PUBLISH 報文中指定的,大部分情況下 Broker 向訂閱者轉發訊息時都會維持原始的 QoS 不變。不過也有一些例外的情況,根據訂閱者的訂閱要求,訊息的 QoS 等級可能會在轉發的時候發生降級。

例如,訂閱者在訂閱時要求 Broker 可以向其轉發的訊息的最大 QoS 等級為 QoS 1,那麼後續所有 QoS 2 訊息都會降級至 QoS 1 轉發給此訂閱者,而所有 QoS 0 和 QoS 1 訊息則會保持原始的 QoS 等級轉發。

1

接下來,讓我們來看看 MQTT 中每個 QoS 等級的具體原理。

QoS 0 - 最多交付一次

QoS 0 是最低的 QoS 等級。QoS 0 訊息即發即棄,不需要等待確認,不需要儲存和重傳,因此對於接收方來說,永遠都不需要擔心收到重複的訊息。

2

為什麼 QoS 0 訊息會丟失?

當我們使用 QoS 0 傳遞訊息時,訊息的可靠性完全依賴於底層的 TCP 協議。

而 TCP 只能保證在連線穩定不關閉的情況下訊息的可靠到達,一旦出現連線關閉、重置,仍有可能丟失當前處於網路鏈路或作業系統底層緩衝區中的訊息。這也是 QoS 0 訊息最主要的丟失場景。

QoS 1 - 至少交付一次

為了保證訊息到達,QoS 1 加入了應答與重傳機制,傳送方只有在收到接收方的 PUBACK 報文以後,才能認為訊息投遞成功,在此之前,傳送方需要儲存該 PUBLISH 報文以便下次重傳。

QoS 1 需要在 PUBLISH 報文中設定 Packet ID,而作為響應的 PUBACK 報文,則會使用與 PUBLISH 報文相同的 Packet ID,以便傳送方收到後刪除正確的 PUBLISH 報文快取。

3

為什麼 QoS 1 訊息會重複?

對於傳送方來說,沒收到 PUBACK 報文分為以下兩種情況:

  1. PUBLISH 未到達接收方
  2. PUBLISH 已經到達接收方,接收方的 PUBACK 報文還未到達傳送方

在第一種情況下,傳送方雖然重傳了 PUBLISH 報文,但是對於接收方來說,實際上仍然僅收到了一次訊息。

但是在第二種情況下,在傳送方重傳時,接收方已經收到過了這個 PUBLISH 報文,這就導致接收方將收到重複的訊息。

4

雖然重傳時 PUBLISH 報文中的 DUP 標誌會被設定為 1,用以表示這是一個重傳的報文。但是接收方並不能因此假定自己曾經接收過這個訊息,仍然需要將其視作一個全新的訊息。

這是因為對於接收方來說,可能存在以下兩種情況:

5

第一種情況,傳送方由於沒有收到 PUBACK 報文而重傳了 PUBLISH 報文。此時,接收方收到的前後兩個 PUBLISH 報文使用了相同的 Packet ID,並且第二個 PUBLISH 報文的 DUP 標誌為 1,此時它確實是一個重複的訊息。

第二種情況,第一個 PUBLISH 報文已經完成了投遞,1024 這個 Packet ID 重新變為可用狀態。傳送方使用這個 Packet ID 傳送了一個全新的 PUBLISH 報文,但這一次報文未能到達對端,所以傳送方後續重傳了這個 PUBLISH 報文。這就使得雖然接收方收到的第二個 PUBLISH 報文同樣是相同的 Packet ID,並且 DUP 為 1,但確實是一個全新的訊息。

由於我們無法區分這兩種情況,所以只能讓接收方將這些 PUBLISH 報文都當作全新的訊息來處理。因此當我們使用 QoS 1 時,訊息的重複在協議層面上是無法避免的。

甚至在比較極端的情況下,例如 Broker 從釋出方收到了重複的 PUBLISH 報文,而在將這些報文轉發給訂閱方的過程中,再次發生重傳,這將導致訂閱方最終收到更多的重複訊息。

在下圖表示的例子中,雖然釋出者的本意只是釋出一條訊息,但對接收方來說,最終卻收到了三條相同的訊息:

6

以上,就是 QoS 1 保證訊息到達帶來的副作用。

QoS 2 - 只交付一次

QoS 2 解決了 QoS 0、1 訊息可能丟失或者重複的問題,但相應地,它也帶來了最複雜的互動流程和最高的開銷。每一次的 QoS 2 訊息投遞,都要求傳送方與接收方進行至少兩次請求/響應流程。

7

  1. 首先,傳送方儲存併傳送 QoS 為 2 的 PUBLISH 報文以啟動一次 QoS 2 訊息的傳輸,然後等待接收方回覆 PUBREC 報文。這一部分與 QoS 1 基本一致,只是響應報文從 PUBACK 變成了 PUBREC。
  2. 當傳送方收到 PUBREC 報文,即可確認對端已經收到了 PUBLISH 報文,傳送方將不再需要重傳這個報文,並且也不能再重傳這個報文。所以此時傳送方可以刪除本地儲存的 PUBLISH 報文,然後傳送一個 PUBREL 報文,通知對端自己準備將本次使用的 Packet ID 標記為可用了。與 PUBLISH 報文一樣,我們需要確保 PUBREL 報文到達對端,所以也需要一個響應報文,並且這個 PUBREL 報文需要被儲存下來以便後續重傳。
  3. 當接收方收到 PUBREL 報文,也可以確認在這一次的傳輸流程中不會再有重傳的 PUBLISH 報文到達,因此回覆 PUBCOMP 報文表示自己也準備好將當前的 Packet ID 用於新的訊息了。
  4. 當傳送方收到 PUBCOMP 報文,這一次的 QoS 2 訊息傳輸就算正式完成了。在這之後,傳送方可以再次使用當前的 Packet ID 傳送新的訊息,而接收方再次收到使用這個 Packet ID 的 PUBLISH 報文時,也會將它視為一個全新的訊息。

為什麼 QoS 2 訊息不會重複?

QoS 2 訊息保證不會丟失的邏輯與 QoS 1 相同,所以這裡我們就不再重複了。

與 QoS 1 相比,QoS 2 新增了 PUBREL 報文和 PUBCOMP 報文的流程,也正是這個新增的流程帶來了訊息不會重複的保證。

在我們更進一步之前,我們先快速回顧一下 QoS 1 訊息無法避免重複的原因。

當我們使用 QoS 1 訊息時,對接收方來說,回覆完 PUBACK 這個響應報文以後 Packet ID 就重新可用了,也不管響應是否確實已經到達了傳送方。所以就無法得知之後到達的,攜帶了相同 Packet ID 的 PUBLISH 報文,到底是傳送方因為沒有收到響應而重傳的,還是傳送方因為收到了響應所以重新使用了這個 Packet ID 傳送了一個全新的訊息。

8

所以,訊息去重的關鍵就在於,通訊雙方如何正確地同步釋放 Packet ID,換句話說,不管傳送方是重傳訊息還是釋出新訊息,一定是和對端達成共識了的。

而 QoS 2 中增加的 PUBREL 流程,正是提供了幫助通訊雙方協商 Packet ID 何時可以重用的能力。

9

QoS 2 規定,傳送方只有在收到 PUBREC 報文之前可以重傳 PUBLISH 報文。一旦收到 PUBREC 報文併發出 PUBREL 報文,傳送方就進入了 Packet ID 釋放流程,不可以再使用當前 Packet ID 重傳 PUBLISH 報文。同時,在收到對端回覆的 PUBCOMP 報文確認雙方都完成 Packet ID 釋放之前,也不可以使用當前 Packet ID 傳送新的訊息。

10

因此,對於接收方來說,能夠以 PUBREL 報文為界限,凡是在 PUBREL 報文之前到達的 PUBLISH 報文,都必然是重複的訊息;而凡是在 PUBREL 報文之後到達的 PUBLISH 報文,都必然是全新的訊息。

一旦有了這個前提,我們就能夠在協議層面完成 QoS 2 訊息的去重。

不同 QoS 的適用場景和注意事項

QoS 0

QoS 0 的缺點是可能會丟失訊息,訊息丟失的頻率依賴於你所處的網路環境,並且可能使你錯過斷開連線期間的訊息,不過優點是投遞的效率較高。

所以我們通常選擇使用 QoS 0 傳輸一些高頻且不那麼重要的資料,比如感測器資料,週期性更新,即使遺漏幾個週期的資料也可以接受。

QoS 1

QoS 1 可以保證訊息到達,所以適合傳輸一些較為重要的資料,比如下達關鍵指令、更新重要的有實時性要求的狀態等。

但因為 QoS 1 還可能會導致訊息重複,所以當我們選擇使用 QoS 1 時,還需要能夠處理訊息的重複,或者能夠允許訊息的重複。

在我們決定使用 QoS 1 並且不對其進行去重處理之前,我們需要先了解,允許訊息的重複,可能意味著什麼。

如果我們不對 QoS 1 進行去重處理,我們可能會遭遇這種情況,釋出方以 1、2 的順序釋出訊息,但最終訂閱方接收到的訊息順序可能是 1、2、1、2。如果 1 表示開燈指令,2 表示關燈指令,我想大部分使用者都不會接受自己僅僅進行了開燈然後關燈的操作,結果燈在開和關的狀態來回變化。

11

QoS 2

QoS 2 既可以保證訊息到達,也可以保證訊息不會重複,但傳輸成本最高。如果我們不願意自行實現去重方案,並且能夠接受 QoS 2 帶來的額外開銷,那麼 QoS 2 將是一個合適的選擇。通常我們會在金融、航空等行業場景下會更多地見到 QoS 2 的使用。

關於 MQTT QoS 的 Q&A

如何為 QoS 1 訊息去重?

在我們介紹 QoS 1 的時候講到,QoS 1 訊息的重複在協議層面上是無法避免的。所以如果我們想要對 QoS 1 訊息進行去重,只能從業務層面入手。

一個比較常用且簡單的方法是,在每個 PUBLISH 報文的 Payload 中都帶上一個時間戳或者一個單調遞增的計數,這樣上層業務就可以根據當前收到訊息中的時間戳或計數是否大於自己上一次接收的訊息中的時間戳或計數來判斷這是否是一個新訊息。

何時向後分發 QoS 2 訊息?

我們已經瞭解到,QoS 2 的流程是非常長的,為了不影響訊息的實時性,我們可以在第一次收到 PUBLISH 報文時,就啟動訊息的向後分發。當然一旦開始向後分發,後續收到在 PUBREL 報文之前到達的 PUBLISH 報文,都不能再重複分發操作,以免訊息重複。

不同 QoS 的效能有差距麼?

以 EMQX 為例,在相同的硬體配置下進行點對點通訊,通常 QoS 0 與 QoS 1 能夠達到的吞吐比較接近,不過 QoS 1 的 CPU 佔用會略高於 QoS 0,負載較高時,QoS 1 的訊息延遲也會進一步增加。而 QoS 2 能夠達到的吞吐一般僅為 QoS 0、1 的一半左右。

結語

至此,相信讀者已對 MQTT QoS 有了深刻的理解。接下來,可訪問 EMQ 提供的 MQTT 入門與進階系列文章學習 MQTT 主題及萬用字元、保留訊息、遺囑訊息等相關概念,探索 MQTT 的更多高階應用,開啟 MQTT 應用及服務開發。

版權宣告: 本文為 EMQ 原創,轉載請註明出處。

原文連結:https://www.emqx.com/zh/blog/introduction-to-mqtt-qos

相關文章