MQTT-保留訊息和遺囑訊息

2023-05-09 18:01:27

遺囑訊息

為什麼需要遺囑訊息

       MQTT的訂閱釋出機制,解耦了訊息的傳送方和接收方,這使我們沒有辦法獲取對端的狀態,為了解決該問題,MQTT提供了遺囑訊息,為意外斷線的使用者端提供了對外發出通知的能力

如何使用遺囑訊息

       使用遺囑訊息,使用者端需要在連線時,也就是connect報文中指定遺囑訊息,除了正常CONNECT報文欄位,需要為遺囑訊息提供以下欄位

Will Topic #遺囑訊息主題

Will QoS  # 遺囑訊息級別

Will Retain  # 將遺囑訊息設定為保留訊息
'''
遺囑訊息一旦釋出,就會在伺服器端的對談狀態中刪除,不能多次消費,我們不能保證遺囑訊息發出的時候訂閱端是否線上,為了避免錯過遺囑訊息,可以使用Will Retain = True 欄位將遺囑訊息設定為保留訊息,這樣訂閱了該主題的使用者端不管什麼時候上線,都可以收到另外一方的離線通知
'''

Will Properties # 遺囑訊息屬性  ↓

Will Delay Interval #遺囑訊息的屬性↑, 設定遺囑訊息的延遲傳送時間
'''
在MQTT 5.0中,可以使用 Will Delay Interval 設定延遲傳送遺囑訊息,單位是S,如果使用者端及時恢復,那麼遺囑訊息的發生倒計時就會終止,可以避免使用者端在短暫離線後恢復,可以繼續服務時但是遺囑訊息已經發出的情況,和保留訊息不同的是,遺囑訊息是對談狀態的一部分,沒有辦法存在比對談更長的時間,如果遺囑延遲時間大於對談過期時間,對談結束的時候遺囑會立即釋出
'''

Will Payload  # 遺囑訊息內容

​ 在使用者端連線成功後,遺囑訊息就會儲存在伺服器端中,一旦使用者端連線異常斷開,伺服器端就會把遺囑訊息傳送給對應的訂閱者,如果使用者端是正常斷開,遺囑訊息則不會發布,在MQTT中,使用者端的意外斷開可以分為以下幾種情況

  • 伺服器端檢測到了一個I/O故障或者網路故障
  • 使用者端在心跳的時間內未能通訊
  • 使用者端在沒有發生Reason Code 為0的DISCONNECT報文的情況下關閉了網路連線
  • 伺服器端在沒有收到DISCONNECT報文的情況下主動關閉了網路連線
  • 在MQTT 3.1.1中,如果滿足任意一個情況,伺服器端會在連線斷開後立刻釋出遺囑訊息,5.0中可以通過設定屬性延遲釋出

保留訊息

為什麼需要保留訊息

       如果不考慮持久對談的因素,那麼MQTT訂閱只有在使用者端連線之後才能建立,所以伺服器端不能提前預知某個主題會被哪些伺服器端訂閱或者某個使用者端會訂閱哪些主題,所以當訊息到達伺服器端之後,伺服器端只會把訊息分發給當前已經存在的訂閱者,分發完成訊息就會從伺服器端中刪除,如果當前沒有任何訂閱者,訊息就會立即丟棄,如果訂閱端在在這之後上線的,就會錯過這個訊息,為了解決該場景的問題,MQTT提供了保留訊息


如何使用保留訊息

       我們可以在傳送PUBLISH的時候把Retain設定為1或者True,表示當前訊息是保留訊息,保留訊息進入伺服器端,會像普通訊息一樣轉發給當前的訂閱者,還會被保留在MQTT的伺服器端中,每當有新的訂閱建立,MQTT伺服器端都會檢索是否存在與這個訂閱匹配的保留訊息,然後把匹配的保留訊息下發給訂閱者,由於訂閱的時候可以使用主題萬用字元,所以可能匹配到多個保留訊息,這些訊息將依次下發給訂閱者

保留訊息的更新

       通過保留訊息,我們可以使訂閱者上線後立即獲得資料更新,不必等待新一次的訊息,每個主題最多可以儲存一條保留訊息,如果主題下的保留訊息已經存在,那麼新到達的保留訊息就會替換原來的保留訊息,保留訊息會一直儲存在伺服器端中,由於他不屬於對談狀態的一部分,所以即便釋出端對談過期,也不會影響保留訊息儲存,如果想要清空這個主題的保留訊息,可以通過傳送一個payload為空的保留訊息來實現,這個為空的保留訊息會合其他保留訊息一樣轉發給訂閱者,區別是在於不會被伺服器端儲存,使用這種方式的話,我們需要確保訂閱端不會把為空的訊息視為一個錯誤


       需要注意的是,QoS 0 可能丟訊息的特性,可能會導致保留訊息刪除不成功,QoS 1 可能重複到達的特性,保留訊息又可能多次刪除,如果不希望出現刪除失敗或者多次刪除的情況,可以使用QoS 2 來發布 payload為空的保留訊息

保留訊息的過期時間

       如果擔心一條保留訊息失去了時效性,還長時間保留在伺服器端中導致被後來的訂閱者消費的話,MQTT 5.0版本可以為保留訊息設定過期時間,PUBLISH的 Message Expiry Interval 屬性,單位是秒

保留訊息的傳送機制

       預設情況下,當保留訊息當成普通訊息向訂閱者轉發的時候,保留訊息中的retain標識會被清除也就是設定為0,只有當新的訂閱建立的時候,傳送保留訊息的retain會設定為1,表示這是一個保留訊息


       針對上述情況多個伺服器端橋接的時候,會衍生一個問題,比如伺服器端A向伺服器端B訂閱了主題,當伺服器端B收到一個保留訊息向伺服器端A轉發的時候清除retain標識,導致伺服器端A收到該保留訊息後只會轉發給訂閱者,不會儲存保留訊息,在MQTT 5.0中,提供了 Retain As Published 的訂閱選項,如果該選項設定為0,就是預設的邏輯,如果設定為1,那麼保留訊息當做普通訊息轉發的時候,Retain標識不會被清除,依然是1


       還有一個選項會影響保留訊息的行為,在某些場景下,雖然使用者端複用了上一次的對談,但是無法確定上一次對談中是否成功訂閱了某個主題,所以只能再次訂閱,如果訂閱已經存在,其實伺服器端已經給使用者端快取了離線期間的訊息,這種情況下,使用者端在重連後,其實並不需要獲取保留訊息,但是現在只要有訂閱建立,訂閱匹配就會下發保留訊息,為了解決這個問題,在MQTT 5.0中,提供了 Retain Handling的訂閱選項

  • Retain Handling = 0,訂閱建立的時候傳送保留訊息
  • Retain Handling = 1,訂閱建立時若該訂閱當前不存在則傳送保留訊息
  • Retain Handling = 2,訂閱建立時不傳送保留訊息

保留訊息的注意事項
  • 在MQTT中,同一條普通訊息只能被同一個使用者端消費一次,保留訊息可能會被重複消費,使用者端進行訂閱,伺服器端下發匹配的保留訊息,即時這個訊息之前已經下發過了,只要保留訊息在使用者端的兩次訂閱期間沒有更新,使用者端就會重複消費到同一條訊息,如果使用者端的訂閱是在保留訊息到達伺服器端之前建立的,訊息轉發後 使用者端重新連線,沒有更新過保留訊息,就是重複收到了兩條同樣的訊息
  • 不能通過主動刪除已經消費過的保留訊息來避免重複,因為可能其他人也使用該主題下的保留訊息,我們就不能去刪除他,其次也沒有辦法正確判斷當前伺服器中的保留訊息有沒有被自己消費過,我們可以參考QoS 1 去重的做法,在保留訊息的payload中增加一個時間戳,訂閱者記錄最後消費的訊息的時間戳,和新到達的保留訊息的時間戳進行比較,如果後者的時間戳更新,就是一個新的訊息,反之就是一個重複的訊息
  • 我們可以通過保留訊息減少訊息的釋出頻次,對於一些固定週期、狀態等以確保新上線的使用者端儘快取得資料,有了保留訊息,我們可以只在狀態發生變更時進行釋出