MQTT 是一個輕量級、客戶端到伺服器、發佈/訂閱訊息協定。MQTT 經過特別設計,旨在減少傳輸開銷(從而減少網路流量)和客戶端裝置上的程式碼佔用空間。因此,MQTT 非常適合感測器和致動器等受限裝置,並且正迅速成為 IoT 的事實標準通訊協定。

Apache ActiveMQ Artemis 支援以下 MQTT 版本(以及其各自規範的連結):

預設情況下,有 acceptor 元素配置為接受連接埠 616161883 上的 MQTT 連線。

請參閱一般協定和互通性章節,以了解有關配置 MQTT 的 acceptor 的詳細資訊。

請參閱 MQTT 範例,以了解此功能的一些實際應用。

1. MQTT 服務品質

MQTT 提供 3 個服務品質等級。

每個訊息(或主題訂閱)都可以定義與其關聯的服務品質。在主題上定義的服務品質等級是客戶端願意接受的最大等級。訊息上的服務品質等級是此訊息所需的服務品質等級。代理程式將嘗試根據訊息和主題訂閱上定義的內容,以最高服務品質等級將訊息傳遞給訂閱者。

每個服務品質等級都提供訊息傳送或接收的保證等級

  • QoS 0:至多一次

    保證特定訊息最多只會被訂閱者接收一次。這確實表示訊息可能永遠不會到達。傳送者和接收者將嘗試傳遞訊息,但如果發生故障且訊息未到達其目的地(例如,由於網路連線),則訊息可能會遺失。此 QoS 的網路流量開銷最少,對客戶端和代理程式的負擔也最少,通常適用於一些資料遺失也沒關係的遙測資料。

  • QoS 1:至少一次

    保證訊息將到達其預定接收者一次或多次。傳送者將繼續傳送訊息,直到收到接收者的確認,確認其已收到訊息。此 QoS 的結果是接收者可能會多次收到訊息,而且網路開銷也會比 QoS 0 高(由於確認)。此外,傳送者也承擔更多負擔,因為它需要儲存訊息並在合理時間內未能收到確認時重試。

  • QoS 2:完全一次

    這是 QoS 中最昂貴的(就網路流量和傳送者及接收者的負擔而言),此 QoS 將確保接收者只會收到訊息一次。這確保接收者永遠不會收到任何重複的訊息副本,並且最終會收到訊息,但代價是需要額外的網路開銷以及傳送者和接收者的複雜性。

2. MQTT 保留訊息

MQTT 有一個有趣的特性,可以「保留」特定位址的訊息。這表示一旦將保留訊息傳送到位址,該位址的任何新訂閱者都將在任何其他訊息之前收到最後傳送的保留訊息。即使保留訊息是在客戶端連線或訂閱之前傳送的,也會發生這種情況。此功能可能有用的範例是在 IoT 等環境中,裝置需要在系統上線時快速取得系統的目前狀態。

保留訊息會儲存在以特殊字首命名的佇列中,該字首根據它們最初傳送的主題名稱而定。例如,傳送到主題 /abc/123 的保留訊息將儲存在名為 $sys.mqtt.retain.abc.123 的多點傳送佇列中,位址名稱相同。MQTT 規範沒有定義保留訊息應儲存多久,因此代理程式將保留此資料,直到客戶端明確刪除保留訊息或它可能過期為止。然而,即使在那個時間點,保留訊息的佇列和位址仍將保留。這些資源可以透過以下 address-setting 自動刪除

<address-setting match="$sys.mqtt.retain.#">
   <auto-delete-queues>true</auto-delete-queues>
   <auto-delete-addresses>true</auto-delete-addresses>
</address-setting>

請記住,也可以自動將expiry-delay套用到保留訊息。

3. 遺囑訊息

當客戶端最初連線到代理程式時,可以傳送遺囑訊息。客戶端能夠將「遺囑訊息」設定為連線封包的一部分。如果客戶端異常斷線,例如由於裝置或網路故障,代理程式將繼續將遺囑訊息發佈到指定的位址(也在連線封包中定義)。遺囑主題的其他訂閱者將收到遺囑訊息,並可以採取相應的動作。此功能在 IoT 風格的場景中很有用,可以偵測到可能大規模部署的裝置上的錯誤。

4. 偵錯記錄

可以透過為 org.apache.activemq.artemis.core.protocol.mqtt 開啟 TRACE 記錄來啟用詳細的協定記錄(例如,封包輸入/輸出)。請按照這些步驟來適當設定記錄。

MQTT 規範沒有規定客戶端發佈的承載格式。就代理程式而言,承載只是一個位元組陣列。然而,為了方便記錄,代理程式會將承載編碼為 UTF-8 字串,並將其列印為最多 256 個字元。承載記錄受到限制,以避免記錄中填滿可能數百 MB 的無用資訊。

5. 持久訂閱

MQTT 會話的訂閱資訊會儲存在名為 $sys.mqtt.sessions 的內部佇列中,並保存到儲存空間(假設已啟用持久性)。此資訊具有持久性,因此 MQTT 訂閱者可以在重新啟動、故障等代理程式之後,無縫地重新連線並恢復其訂閱。當代理程式設定為高可用性時,此資訊將在備份上提供,因此即使在代理程式容錯移轉的情況下,訂閱者也能夠恢復其訂閱。

雖然持久訂閱可能很方便,但由於必須將資料寫入儲存空間,因此會造成效能損失。如果您不需要便利性(例如,您總是使用乾淨的會話),而且您不想要效能損失,則可以透過在 broker.xml 中為 $sys.mqtt.sessions 佇列停用持久性來停用它,例如

<addresses>
   ...
   <address name="$sys.mqtt.sessions">
      <anycast>
         <queue name="$sys.mqtt.sessions">
            <durable>false</durable>
         </queue>
      </anycast>
   </address>
   ...
</addresses>

設定 mqtt-session-state-persistence-timeout 控制代理程式在擲回錯誤之前等待將資料寫入儲存空間的時間長度。以毫秒為單位測量。預設值為 5000

6. 自訂用戶端 ID 處理

MQTT 應用程式使用的用戶端 ID 非常重要,因為它可以唯一識別應用程式。在某些情況下,代理程式管理員可能想要執行額外的驗證,甚至修改傳入的用戶端 ID,以支援特定的使用案例。這可以透過實作自訂安全性管理員來實現,如 security-manager 範例中所示。

最簡單的實作是「封裝器」,就像 security-manager 範例所使用的一樣。在 authenticate 方法中,您可以使用傳入的 org.apache.activemq.artemis.spi.core.protocol.RemotingConnection 上的 setClientId() 來修改用戶端 ID。如果您執行用戶端 ID 的一些自訂驗證,則可以透過擲回 org.apache.activemq.artemis.core.protocol.mqtt.exceptions.InvalidClientIdException 來拒絕用戶端 ID。

7. 通配符訂閱

MQTT 為主題篩選器定義了特殊的通配符語法。此定義位於 3.1.15 規格的 4.7.1 節中。MQTT 主題是階層式的,很像檔案系統,它們使用特殊字元(即預設為 /)來分隔階層式層級。訂閱者可以訂閱特定主題或整個階層分支。

若要訂閱位址階層的分支,訂閱者可以使用通配符。MQTT 中有 2 種通配符

  • 多層級 (#)

    將此通配符新增至位址將比對指定節點下的位址階層的所有分支。例如:/uk/ 會比對 /uk/cities/uk/cities/newcastle/uk/rivers/tyne。訂閱位址 將導致訂閱代理程式中的所有主題。這可能很有用,但應該謹慎執行,因為它具有顯著的效能影響。

  • 單層級 (+)

    比對位址階層中的單一層級。例如,/uk/+/stores 會比對 /uk/newcastle/stores,但不比對 /uk/cities/newcastle/stores

接近預設的通配符語法,但不完全相同。因此,需要進行一些轉換。此轉換不是免費的,因此如果您想要最佳的 MQTT 效能,請使用 broker.xml 來設定通配符語法以符合 MQTT 的語法,例如

<core>
   ...
   <wildcard-addresses>
      <delimiter>/</delimiter>
      <any-words>#</any-words>
      <single-word>+</single-word>
   </wildcard-addresses>
   ...
</core>

當然,變更預設語法也表示其他協定上的其他客戶端也需要遵循此相同的語法以及您的 address-setting 組態元素的 match 值。

8. Web Sockets

Apache ActiveMQ Artemis 也支援透過 Web Sockets 的 MQTT。支援 Web Sockets 的現代網頁瀏覽器可以傳送和接收 MQTT 訊息。

透過一般的 MQTT 接收器支援 Web Sockets 上的 MQTT。

<acceptor name="mqtt-ws-acceptor">tcp://host:1883?protocols=MQTT</acceptor>

透過此設定,Apache ActiveMQ Artemis 將接受在連接埠 1883 上透過 Web Sockets 的 MQTT 連線。然後,網頁瀏覽器可以使用 Web Socket 連線到 ws://<server>:1883,以傳送和接收 MQTT 訊息。

SSL/TLS 也可用,例如:

<acceptor name="mqtt-wss-acceptor">tcp://host:8883?protocols=MQTT;sslEnabled=true;keyStorePath=/path/to/keystore;keyStorePassword=myPass</acceptor>

然後,網頁瀏覽器可以使用 Web Socket 連線到 wss://<server>:8883,以傳送和接收 MQTT 訊息。

MQTT 規範定義了一種通常稱為「連線竊取」的行為。這表示每當新的用戶端使用與另一個現有用戶端相同的用戶端 ID 連線時,現有用戶端的連線將會關閉,且其網路連線將會終止。

在某些使用案例中,不希望出現這種行為,因此它是可設定的。可以在 MQTT acceptor 上設定 URL 參數 allowLinkStealing 以修改此行為。預設情況下,allowLinkStealingtrue。如果將其設定為 false,則每當新的用戶端使用與另一個現有用戶端相同的用戶端 ID 連線時,新的用戶端連線將會關閉,且其網路連線將會終止。對於 MQTT 5 用戶端,它們將會收到 0x80 (即「未指定的錯誤」) 的中斷連線原因代碼。

10. 自動訂閱清除

有時,使用 CleanSession=false 的 MQTT 3.x 用戶端不會正確取消訂閱。可以在 MQTT acceptor 上設定 URL 參數 defaultMqttSessionExpiryInterval,以便在逾時間隔過去後自動清除已放棄的連線和訂閱佇列。

MQTT 5 具有相同的基本語義,但配置略有不同。CleanSession 旗標已替換為 CleanStart連線過期間隔 屬性。如果用戶端已設定連線過期間隔,代理程式將會使用該間隔。如果未設定,則代理程式將會套用 defaultMqttSessionExpiryInterval

預設的 defaultMqttSessionExpiryInterval-1,這表示 MQTT 3.x 用戶端或未傳遞其連線過期間隔的 MQTT 5 用戶端不會進行清除。否則,它表示用戶端中斷連線後必須經過的秒數,代理程式才會移除連線狀態和訂閱佇列。

MQTT 連線狀態預設每 5,000 毫秒掃描一次。可以使用在 broker.xmlcore 區段中設定的 mqtt-session-scan-interval 元素來變更此設定。

11. 流量控制

MQTT 5 引入了一種簡單的 流量控制形式。簡而言之,代理程式可以告訴用戶端,在收到確認之前它可以接收多少 QoS 1 和 2 訊息,反之亦然。

這是透過在 broker.xml 的 MQTT acceptor 上設定 receiveMaximum URL 參數,在代理程式上控制的。

預設值為 65535 (MQTT 使用的 2 位元組整數的最大值)。

MQTT 5 規範禁止值為 0

值為 -1 將會防止代理程式通知用戶端任何接收上限,這表示將會停用從用戶端到代理程式的流量控制。這實際上與將值設定為 65535 相同,但減少了 CONNACK 封包的大小幾個位元組。

12. 主題別名上限

MQTT 5 引入了 主題別名。這是 PUBLISH 控制封包大小的優化,因為現在可以使用 2 位元組的整數值來取代主題的_名稱,這可能會很長。

用戶端和代理程式可以互相通知它們支援的_最大_別名值 (即,它們支援多少不同的別名)。這是透過在 MQTT 用戶端使用的 acceptor 上使用 topicAliasMaximum URL 參數,在代理程式上控制的。

預設值為 65535 (MQTT 使用的 2 位元組整數的最大值)。

值為 0 將會停用從用戶端到代理程式的主題別名。

值為 -1 將會防止代理程式通知用戶端任何主題別名上限,這表示將會停用從用戶端到代理程式的別名。這實際上與將值設定為 0 相同,但減少了 CONNACK 封包的大小幾個位元組。

13. 最大封包大小

MQTT 5 引入了最大封包大小。這是伺服器或用戶端願意接受的最大封包大小。

這是透過在 broker.xml 的 MQTT acceptor 上設定 maximumPacketSize URL 參數,在代理程式上控制的。

預設值為 268435455 (即 256MB - MQTT 使用的可變位元組整數的最大值)。

MQTT 5 規範禁止值為 0

值為 -1 將會防止代理程式通知用戶端任何最大封包大小,這表示不會對傳入封包的大小強制執行任何限制。這也會減少 CONNACK 封包的大小幾個位元組。

14. 伺服器保持連線

所有 MQTT 版本都支援由_用戶端_定義的連線保持連線值。MQTT 5 引入了伺服器保持連線值,以便代理程式可以定義用戶端應使用的值。伺服器保持連線的主要用途是讓伺服器通知用戶端,由於不活動,它會比用戶端指定的保持連線時間更早中斷用戶端的連線。

這是透過在 broker.xml 的 MQTT acceptor 上設定 serverKeepAlive URL 參數,在代理程式上控制的。

預設值為 60,以為單位測量。

值為 0 會完全停用保持連線,無論用戶端的保持連線值為何。由於停用保持連線通常被認為是危險的,因為這可能會導致資源耗盡,因此不建議這樣做。

值為 -1 表示代理程式將會始終接受用戶端的保持連線值 (即使該值為 0)。

任何其他值表示,如果 serverKeepAlive 小於用戶端的保持連線值,則將會套用 serverKeepAlive除非用戶端的保持連線值為 0,在這種情況下將會套用 serverKeepAlive。這是因為值為 0 將會停用保持連線,而停用保持連線通常被認為是危險的,因為這可能會導致資源耗盡。

15. 增強的驗證

MQTT 5 引入了增強的驗證,將現有的名稱和密碼驗證擴展為包括挑戰/回應樣式的驗證。

但是,目前沒有實作任何挑戰/回應機制,因此如果用戶端在其 CONNECT 封包中傳遞「驗證方法」屬性,則它將會收到原因代碼為 0x8C (即錯誤的驗證方法) 的 CONNACK,並且網路連線將會關閉。

16. 發佈授權失敗

MQTT 3.1.1 規範對於由於缺乏授權而導致 PUBLISH 封包失敗時代理程式的行為不夠明確。在第 3.3.5 節中指出

如果伺服器實作不授權用戶端執行 PUBLISH;它無法通知該用戶端。它必須根據正常的 QoS 規則進行肯定確認,或關閉網路連線

預設情況下,代理程式將會關閉網路連線。但是,如果您希望代理程式進行肯定確認,請在 broker.xml 中相關的 MQTT acceptor 上,將 URL 參數 closeMqttConnectionOnPublishAuthorizationFailure 設定為 false,例如:

<acceptor name="mqtt">tcp://0.0.0:1883?protocols=MQTT;closeMqttConnectionOnPublishAuthorizationFailure=false</acceptor>