1. MQTT中的QoS等級(jí)
MQTT設(shè)計(jì)了一套保證消息穩(wěn)定傳輸?shù)臋C(jī)制扇调,包括消息應(yīng)答、存儲(chǔ)和重傳。
為了保證消息被正確的接收
在這套機(jī)制下蟀伸,提供了三種不同層次QoS(Quality of Service):
- QoS0,At most once缅刽,至多一次啊掏;
- QoS1,At least once衰猛,至少一次迟蜜;
- QoS2,Exactly once啡省,確保只有一次娜睛。
QoS 是消息的發(fā)送方(Sender)和接受方(Receiver)之間達(dá)成的一個(gè)協(xié)議:
- QoS0 代表髓霞,Sender 發(fā)送的一條消息,Receiver 最多能收到一次微姊,也就是說(shuō) Sender 盡力向 Receiver 發(fā)送消息酸茴,如果發(fā)送失敗,也就算了兢交;
- QoS1 代表薪捍,Sender 發(fā)送的一條消息,Receiver 至少能收到一次配喳,也就是說(shuō) Sender 向 Receiver 發(fā)送消息酪穿,如果發(fā)送失敗,會(huì)繼續(xù)重試晴裹,直到 Receiver 收到消息為止被济,但是因?yàn)橹貍鞯脑颍琑eceiver 有可能會(huì)收到重復(fù)的消息涧团;
- QoS2 代表只磷,Sender 發(fā)送的一條消息,Receiver 確保能收到而且只收到一次泌绣,也就是說(shuō) Sender 盡力向 Receiver 發(fā)送消息钮追,如果發(fā)送失敗,會(huì)繼續(xù)重試阿迈,直到 Receiver 收到消息為止元媚,同時(shí)保證 Receiver 不會(huì)因?yàn)橄⒅貍鞫盏街貜?fù)的消息。
::: warning
QoS是Sender和Receiver之間的協(xié)議苗沧,而不是Publisher和Subscriber之間的協(xié)議刊棕。
換句話(huà)說(shuō),Publisher發(fā)布了一條QoS1的消息待逞,只能保證Broker能至少收到一次這個(gè)消息甥角;
而對(duì)于Subscriber能否至少收到一次這個(gè)消息,還要取決于Subscriber在Subscribe的時(shí)候和Broker協(xié)商的QoS等級(jí)识樱。
:::
1.1. QoS0
QoS0等級(jí)下蜈膨,Sender和Receiver之間一次消息的傳遞流程如下:
Sender向Receiver發(fā)送一個(gè)包含消息數(shù)據(jù)的PUBLISH包,然后不管結(jié)果如何牺荠,丟掉已發(fā)送的PUBLISH包,一條消息的發(fā)送完成驴一。
1.2. QoS1
QoS1要保證消息至少到達(dá)一次休雌,所以有一個(gè)應(yīng)答的機(jī)制。Sender和Receiver的一次消息的傳遞流程如下:
1.Sender向Receiver發(fā)送一個(gè)帶有數(shù)據(jù)的PUBLISH包肝断,并在本地保存這個(gè)PUBLISH包杈曲;
2.Receiver收到PUBLISH包以后驰凛,向Sender發(fā)送一個(gè)PUBACK數(shù)據(jù)包,PUBACK數(shù)據(jù)包沒(méi)有消息體(Payload)担扑,在可變頭中有一個(gè)包標(biāo)識(shí)(Packet Identifier)恰响,和它收到的PUBLISH包中的Packet Identifier一致。
3.Sender收到PUBACK之后涌献,根據(jù)PUBACK包中的Packet Identifier找到本地保存的PUBLISH包胚宦,然后丟棄掉,一次消息的發(fā)送完成燕垃。
但是消息傳遞流程中可能會(huì)出現(xiàn)問(wèn)題:
- 如果Sender在一段時(shí)間內(nèi)沒(méi)有收到PUBLISH包對(duì)應(yīng)的PUBACK枢劝,它將該P(yáng)UBLISH包的DUP標(biāo)識(shí)設(shè)為1(代表是重新發(fā)送的PUBLISH包),然后重新發(fā)送該P(yáng)UBLISH包卜壕。
- Receiver可能會(huì)重復(fù)收到消息您旁,需自行去重。
1.3. QoS2
相比QoS0和QoS1,QoS2不僅要確保Receiver能收到Sender發(fā)送的消息轴捎,還需要確保消息不重復(fù)鹤盒。它的重傳和應(yīng)答機(jī)制就要復(fù)雜一些,同時(shí)開(kāi)銷(xiāo)也是最大的侦副。QoS2下侦锯,一次消息的傳遞流程如下所示:
1.Sender發(fā)送QoS為2的PUBLISH數(shù)據(jù)包,數(shù)據(jù)包 Packet Identifier 為 P跃洛,并在本地保存該P(yáng)UBLISH包率触;
2.Receiver收到PUBLISH數(shù)據(jù)包后,在本地保存PUBLISH包的Packet Identifier P汇竭,并回復(fù)Sender一個(gè)PUBREC數(shù)據(jù)包葱蝗,PUBREC數(shù)據(jù)包可變頭中的Packet Identifier為P,沒(méi)有消息體(Payload)细燎;
3.當(dāng)Sender收到PUBREC两曼,它就可以安全的丟棄掉初始Packet Identifier為P的PUBLISH數(shù)據(jù)包。同時(shí)保存該P(yáng)UBREC數(shù)據(jù)包玻驻,并回復(fù)Receiver一個(gè)PUBREL數(shù)據(jù)包悼凑,PUBREL數(shù)據(jù)包可變頭中的Packet Identifier為P,沒(méi)有消息體璧瞬;
4.當(dāng)Receiver收到PUBREL數(shù)據(jù)包户辫,它可以丟掉保存的PUBLISH包的Packet Identifier P,并回復(fù)Sender一個(gè)可變頭中 Packet Identifier 為 P嗤锉,沒(méi)有消息體(Payload)的PUBCOMP數(shù)據(jù)包渔欢;
5.當(dāng)Sender收到PUBCOMP包,那么認(rèn)為傳輸已完成瘟忱,則丟掉對(duì)應(yīng)的PUBREC數(shù)據(jù)包奥额;
上面是一次完整無(wú)誤的傳輸過(guò)程苫幢,然而傳輸過(guò)程中可能會(huì)出現(xiàn)以下情況:
- 情況1:Sender發(fā)送PUBLISH數(shù)據(jù)包給Receiver的時(shí)候,發(fā)送失數姘ぁ韩肝;
- 情況2:Sender已經(jīng)成功發(fā)送PUBLISH數(shù)據(jù)包給Receiver了,但是Receiver發(fā)送PUBREC數(shù)據(jù)包失斁爬啤哀峻;
- 情況3:Sender已經(jīng)成功收到了PUBREC數(shù)據(jù)包,但是PUBREL數(shù)據(jù)包發(fā)送失斨闾搿谜诫;
- 情況4:Receiver已經(jīng)收到了PUBREL數(shù)據(jù)包,但是發(fā)送PUBCOMP數(shù)據(jù)包時(shí)發(fā)送失敗
針對(duì)上述的問(wèn)題攻旦,較為詳細(xì)的處理方法如下:
不管是情況1還是情況2喻旷,因?yàn)镾ender在一定時(shí)間內(nèi)沒(méi)有收到PUBREC,那么它會(huì)把PUBLISH包的DUP標(biāo)識(shí)設(shè)為1牢屋,重新發(fā)送該P(yáng)UBLISH數(shù)據(jù)包且预;
不管是情況3還是情況4,因?yàn)镾ender在一定時(shí)間內(nèi)沒(méi)有收到PUBCOMP包烙无,那么它會(huì)重新發(fā)送PUBREL數(shù)據(jù)包锋谐;
針對(duì)情況2,Receiver可能會(huì)收到多個(gè)重復(fù)的PUBLISH包截酷,更加完善的處理如下:
Receiver在收到PUBLISH數(shù)據(jù)包之后涮拗,馬上回復(fù)一個(gè)PUBREC數(shù)據(jù)包。并會(huì)在本地保存PUBLISH包的Packet Identifier P迂苛,不管之后因?yàn)橹貍鞫嗌俅芜@個(gè)Packet Identifier 為P的數(shù)據(jù)包三热,Receiver都認(rèn)為是重復(fù)的,丟棄三幻。同時(shí)Receiver接收到QoS為2的PUBLISH數(shù)據(jù)包后就漾,并不馬上投遞給上層,而是在本地做持久化念搬,將消息保存起來(lái)(這里需要是持久化而不是保存在內(nèi)存)抑堡。
針對(duì)情況4,更加完善的處理如下:
Receiver收到PUBREL數(shù)據(jù)包后朗徊,正式將消息遞交給上層應(yīng)用層首妖,投遞之后銷(xiāo)毀Packet Identifier P,并發(fā)送PUBCOMP數(shù)據(jù)包爷恳,銷(xiāo)毀之前的持久化消息悯搔。
之后不管接收到多少個(gè)PUBREL數(shù)據(jù)包,因?yàn)闆](méi)有Packet Identifier P,直接回復(fù)PUBCOMP數(shù)據(jù)包即可妒貌。
2. QoS降級(jí)
在 MQTT 協(xié)議中,從 Broker 到 Subscriber 這段消息傳遞的實(shí)際 QoS 等于:Publisher 發(fā)布消息時(shí)指定的 QoS 等級(jí)和 Subscriber 在訂閱時(shí)與 Broker 協(xié)商的 QoS 等級(jí)铸豁,這兩個(gè) QoS 等級(jí)中的最小那一個(gè)灌曙。
Actual Subscribe QoS = MIN(Publish QoS, Subscribe QoS)
3. QoS和會(huì)話(huà)
如果 Client 想接收離線(xiàn)消息,必須使用持久化的會(huì)話(huà)(Clean Session = 0)連接到 Broker节芥,這樣 Broker 才會(huì)存儲(chǔ) Client 在離線(xiàn)期間沒(méi)有確認(rèn)接收的 QoS 大于 等于1 的消息在刺。
在發(fā)送QoS為1或2的情況,Broker(此時(shí)為Sender)會(huì)將發(fā)送的PUBLISH數(shù)據(jù)包保存到本地头镊,直到收到一系列回復(fù)的數(shù)據(jù)包蚣驼,
然而Client(此時(shí)為Receiver)在離線(xiàn)期間無(wú)法回復(fù)相應(yīng)的數(shù)據(jù)包,所以會(huì)一直存儲(chǔ)相艇。
4. QoS等級(jí)使用建議
在以下情況下你可以選擇 QoS0:
- Client 和 Broker 之間的網(wǎng)絡(luò)連接非常穩(wěn)定颖杏,例如一個(gè)通過(guò)有線(xiàn)網(wǎng)絡(luò)連接到 Broker 的測(cè)試用 Client;
- 可以接受丟失部分消息坛芽,比如你有一個(gè)傳感器以非常短的間隔發(fā)布狀態(tài)數(shù)據(jù)留储,所以丟一些也可以接受;
- 你不需要離線(xiàn)消息咙轩。
在以下情況下你應(yīng)該選擇 QoS1:
- 你需要接收所有的消息获讳,而且你的應(yīng)用可以接受并處理重復(fù)的消息;
- 你無(wú)法接受 QoS2 帶來(lái)的額外開(kāi)銷(xiāo)活喊,QoS1 發(fā)送消息的速度比 QoS2 快很多丐膝。
在以下情況下你應(yīng)該選擇 QoS2:
- 你的應(yīng)用必須接收到所有的消息,而且你的應(yīng)用在重復(fù)的消息下無(wú)法正常工作钾菊,同時(shí)你也能接受 QoS2 帶來(lái)的額外開(kāi)銷(xiāo)帅矗。