在任何網絡環境下,都會出現一方鏈接失敗,好比離開公司大門那一刻沒有了WIFI信號。但持續鏈接的另外一端-服務器可能不能當即知道對方已斷開。相似網絡異常狀況,都有可能在消息發送的過程當中出現,消息發送出去,就丟失了。html
MQTT協議假定客戶端和服務器端穩定狀況通常,彼此之通訊管道不可靠,一旦客戶端網絡斷開,狀況就會很嚴重,很難恢復原狀。java
但別忘記,不少客戶端會有永久性存儲設備支持,好比閃存ROM、存儲卡等,在通訊出現異常的狀況下能夠用於保存關鍵數據或狀態信息等。服務器
總之,異常網絡狀況很複雜,只能當心處理之。網絡
在QoS > 0狀況下,PUBLISH、PUBREL、SUBSCRIBE、UNSUBSCRIBE等類型消息在發送者發送完以後,須要等待一個響應消息,若在一個指定時間段內沒有收到,發送者可能須要重試。重發的消息,要求DUP標記要設置爲1.session
等待響應的超時應該在消息成功發送以後開始算起,而且等待超時應該是能夠配置選項,以便在下一次重試的時候,適當加大。好比第一次重試超時10秒,下一次可能爲20秒,再一次重試可能爲60秒呢。固然,還要有一個重試次數限制的。.net
還 有一種狀況,客戶端從新鏈接,但未在可變頭部中設置clean session標記,但雙方(客戶端和服務器端)都應該重試先前未發送的動態消息(in-flight messages)。客戶端不被強制要求發送未被確認的消息,但服務器端就得須要重發那些未被去確認的消息。翻譯
QoS level爲Quality of Service level的縮寫,翻譯成中文,服務質量等級。code
MQTT 3.1協議在"4.1 Quality of Service levels and flows"章節中,僅僅討論了客戶端到服務器的發佈流程,不太完整。由於決定消息到達率,可以提高發送質量的,應該是服務器發佈PUBLISH消息到訂閱者這一消息流方向。htm
至多發送一次,發送即丟棄。沒有確認消息,也不知道對方是否收到。blog
Client
Server | ||
---|---|---|
QoS = 0 | PUBLISH ----------> |
Action: Publish message to subscribers then Forget Reception: <=1 |
針對的消息不重要,丟失也無所謂。
網絡層面,傳輸壓力小。
全部QoS level 1都要在可變頭部中附加一個16位的消息ID。
SUBSCRIBE和UNSUBSCRIBE消息使用QoS level 1。
針對消息的發佈,Qos level 1,意味着消息至少被傳輸一次。
發送者若在一段時間內接收不到PUBACK消息,發送者須要打開DUB標記爲1,而後從新發送PUBLISH消息。所以會致使接收方可能會收到兩次PUBLISH消息。針對客戶端發佈消息到服務器的消息流:
Client
Server | ||
---|---|---|
QoS = 1 DUP = 0 Message ID = x Action: Store message |
PUBLISH ----------> |
Actions:
Reception: >=1
|
Action: Discard message | PUBACK <---------- |
Message ID = x
|
針對服務器發佈到訂閱者的消息流:
Server
Subscriber | ||
---|---|---|
QoS = 1 DUP = 0 Message ID = x |
PUBLISH ----------> |
Actions:
Reception: >=1
|
PUBACK <---------- |
Message ID = x
|
發 布者(客戶端/服務器)若因種種異常接收不到PUBACK消息,會再次從新發送PUBLISH消息,同時設置DUP標記爲1。接收者以服務器爲例,這可能 會致使服務器收到重複消息,按照流程,broker(服務器)發佈消息到訂閱者(會致使訂閱者接收到重複消息),而後發送一條PUBACK確認消息到發佈 者。
在業務層面,或許能夠彌補MQTT協議的不足之處:重試的消息ID必定要一致接收方必定判斷當前接收的消息ID是否已經接受過
但同樣不可以徹底確保,消息必定到達了。
僅僅在PUBLISH類型消息中出現,要求在可變頭部中要附加消息ID。
級別高,通訊壓力稍大些,但確保了僅僅傳輸接收一次。
先看協議中流程圖,Client -> Server方向,會有一個整體印象:
Client
Server | ||
---|---|---|
QoS = 2 DUP = 0 Message ID = x Action: Store message |
PUBLISH ----------> |
Action(a) Store message or Actions(b):
|
PUBREC <---------- |
Message ID = x | |
Message ID = x | PUBREL ----------> |
Actions(a):
or Action(b): Delete message ID |
Action: Discard message | PUBCOMP <---------- |
Message ID = x |
Server -> Subscriber:
Server
Subscriber | ||
---|---|---|
QoS = 2 DUP = 0 Message ID = x |
PUBLISH ----------> |
Action: Store message |
PUBREC <---------- |
Message ID = x | |
Message ID = x | PUBREL ----------> |
Actions:
|
PUBCOMP <---------- |
Message ID = x |
Server 端採起的方案a和b,都包含了什麼時候消息有效,什麼時候處理消息。兩個方案二選一,Server端本身決定。但不管死採起哪種方式,都是在QoS level 2協議範疇下,不受影響。若一方沒有接收到對應的確認消息,會從最近一次須要確認的消息重試,以便整個(QoS level 2)流程打通。
消息順序會受許多因素的影響,但對於服務器程序,必須保證消息傳遞流程的每一個階段要和開始的順序一致。例如,在QoS level 2定義的消息流中,PUBREL流必須和PUBLISH流具備相同的順序發送:
Client
Server | ||
---|---|---|
PUBLISH 1----------> PUBLISH 2 ----------> PUBLISH 3 ----------> |
||
PUBREC 1<---------- PUBREC 2 <---------- |
||
PUBREL 1----------> |
||
PUBREC 3<---------- |
||
PUBREL 2----------> |
||
PUBCOMP 1<---------- |
||
PUBREL 3----------> |
||
PUBCOMP 2<---------- PUBCOMP 3 <---------- |
流動消息(in-flight messages)數量容許有一個可保證的效果:
在MQTT協議中,PUBLISH消息固定頭部RETAIN標記,只有爲1纔要求服務器須要持久保存此消息,除非新的PUBLISH覆蓋。
對於持久的、最新一條PUBLISH消息,服務器不但要發送給當前的訂閱者,而且新的訂閱者(new subscriber,一樣須要訂閱了此消息對應的Topic name)會立刻獲得推送。
Tip:新來乍到的訂閱者,只會取出最新的一個RETAIN flag = 1的消息推送,不是全部。
mqtt的消息流,以小和輕量爲主要目標,QoS級別進行了消息傳輸的保證。