MQTT(Message Queuing Telemetry Transport)是一种轻量级的通信协议,在物联网和消息传递系统中广泛应用。MQTT 提供了三个不同的 QoS(Quality of Service)等级,用于确保消息的可靠性和传输效率。本文将详细介绍 MQTT 的 QoS 等级之间的区别,包括各自的特点、适用场景和性能表现。
消息质量:QoS 0 低 < QoS 1中 < QoS 2高
QoS 0:最多一次交付
QoS 0 是 MQTT 中最简单的交付等级。在 QoS 0 下,消息发布后,对消息的投递没有任何确认或重传机制。这意味着消息可能会有丢失或传输失败的风险。
- 最多一次交付:消息发布后,至多会被传递一次,但不保证被成功接收。
- 无需确认或重传:不会花费额外的网络传输或处理开销。
- 低延迟:由于没有确认和重传机制,消息传输速度更快。
QoS 0 适用于以下场景:
- 无需保证消息可靠性的应用场景,例如天气预报、传感器数据等。
- 带宽受限的网络环境,因为 QoS 0 不会产生额外的网络传输开销。
QoS 1:至少一次交付
QoS 1 是 MQTT 中的中等交付等级。在 QoS 1 下,消息发布后,至少会被传递一次,但可能存在重复传递的情况。
- 至少一次交付:消息发布后,将确保至少被传递一次,但可能会多次传递。
- 确认和重传:如果消息未能成功传递给订阅者,MQTT 客户端会进行确认和重传处理。
- 可靠性较高:相对于 QoS 0,QoS 1 提供了更高的消息传输可靠性。
QoS 1 适用于以下场景:
- 需要确保消息至少被传递一次的应用场景,例如传感器数据采集、远程控制等。
- 带宽充足的网络环境,因为 QoS 1 需要进行确认和重传,会产生一定的网络传输开销。
为什么QoS 1无法避免接收到重复消息?
当我们使用QOS1的时候,在pub ack包发送之后无论ack包是否到达发布者,PacketID都会被复用,也就说订阅者无法判断收到具有相同PacketID的消息是由于发布者未收到ACK报文进行的重传,还是复用之前的PacketID发送的新消息,这就是QoS1无法避免收到重复消息的原因。
QoS 2:只有一次交付
QoS 2 是 MQTT 中最高的交付等级。在 QoS 2 下,消息发布后,只会被传递一次,不会发生重复传递的情况。
- 只有一次交付:消息发布后,将确保仅被传递一次,不会发生重复传递。
- 确认和重传:如果消息未能成功传递给订阅者,MQTT 客户端会进行确认和重传处理,直到消息被接收为止。
- 最高可靠性:相对于 QoS 0 和 QoS 1,QoS 2 提供了最高的消息传输可靠性。
QoS 2 适用于以下场景:
- 需要确保消息仅被传递一次的关键应用场景,例如金融交易、远程医疗等。
- 带宽充足的网络环境,因为 QoS 2 需要进行确认和重传,会产生较大的网络传输开销。
四次交互在三次交互基础上增加了一个pubcomp包,当publiser与server收到pubcomp包后,表示接收端已经收到release包了,因此可以删除msg了。到此,QOS=2实现了包的去重与msgid的可重用。
为什么QoS2的报文不会重复
QOS 2 使用PUBLISH和PUBREC报文来保证消息的到达,原理和QoS1一致,新增的PUBREL和PUBCOMP来保证消息不会重复。QOS2规定,发送端只有在收到PUBREC报文之前,才可以重传PUBLISH报文,而一旦收到了PUBREC报文并且发出了PUBREL报文,发送端就进入了Packet ID的释放流程,在收到接收端的PUBCOMP响应之前,发送端既不会使用该Packet ID重传消息,也不能用于发布新的消息,只有收到PUBCOMP响应之后,发送端菜可以继续使用这个Packet ID,对于接收端来说,可以以PUBREL为界限,凡是PUBREL之前到达的PUBLISH报文,必然是重复的消息,PUBREL之后到达的消息,全是新消息。
分发QoS2的消息
如果接收端必须等到QOS2流程结束才能向后分发消息,如果网络不好的情况下,消息的实时性会受到很大的影响。所以MQTT允许接收端第一次受到PUBLISH报文的时候,就启动消息的向后分发,一旦分发过之后,后续再受到重传报文,接收端就不能再进行分发操作了。
MQTT头中重要的标志位
message ID
只有当QoS等级是1或2时,报文标识符(Packet Identifier)字段才能出现在PUBLISH报文中。
消息ID用16位无符号整数来表示,在同一个方向上的在传消息ID必须是唯一的。它通常是逐个消息递增的,但不强制如此。
客户端与它所连接的服务器一样,都需要维护自己的消息ID列表,二者的消息ID列表互不影响。客户端在发送一个消息ID为1的 PUBLISH 消息的同时也有可能收到来自服务器的消息ID为1的 PUBLISH 消息。
表示消息ID的2个字节的顺序为先 MSB,再 LSB(大端模式)。
不要使用值为0的消息ID。它是作为无效消息ID保留的。
一个消息ID用来匹配一个消息的交互确认,在QOS=1时用于标识一对报文,QOS=2时用于标识4个报文,即一个完整的消息通信及确认。
只有两次包交互看似已经完成了Exactly once delivery,但是subscribe端没有释放messageId,因为subscriber端不知道server是否收到subscriber发出的ack消息,所以需要一直保存msg或者msgId,来对server端超时重发的msg去重。而我们看到messageID的设计只有2个字节长度,也就是65535个,如果subscribe一直不删除本地messageID,那第二轮的messageID会冲掉第一轮的messageID,因此必须释放本地messageID。为什么把messageID设计的这么小呢,因为MQTT就是为那些小的硬件设备设计的,他们没有足够的内存与磁盘保存海量的msg。从上面可以看出来,两次交互不够,至少还需要一次交互告诉subscriber删除本地msg。对于server端也是如此,server也需要一直保存msg来防止publisher重发。
Retained
retained= 0
,服务端不能存储这个消息也不能移除或替换任何现存的保留消息。
true
,那么MQTT服务器就会保存该条消息!
Clean session Flag
对于客户端:可在connect报文中设置clean session标志位,0表示不清理会话,1表示清理会话。若在CONNECT报文中设置cleansession为0,且服务器回复的CONNACK报文中的Session Present位为1,则表示当前连接将会复用服务器保存的会话。
对于服务端:根据客户端CONNECT报文中的CleanSession标志位,来决定是否保存此次连接中客户端所订阅的主题的记录,在客户端断开连接后,服务器还得保存后面往该主题发送的消息。
客户端的连接报文中,CleanSession标志位设置为0,服务端会保存此次客户端订阅的所有主题,连接断开后,仍然保存订阅的消息。在客户端下线后,如果服务端接收到这些主题的消息,服务器会保存这些主题的消息,在客户端上线后,再往客户端推送这些主题的消息。
Keep Alive
当服务器在Keep Alive时间的1.5倍以上时间未收到设备心跳包时,则认为设备已经掉线了。此时服务器将会向设备设置的遗嘱消息主题发送遗嘱消息内容。
设备通知MQTT服务器KeepAlive的时间值为60秒,则设备必须在90秒内向服务器发送心跳包或者进行一次数据通信,否则服务器认为设备掉线。并关闭对应的MQTT TCP连接信息。
Dup Flag
如果DUP标志被设置为0,表示这是客户端或服务端第一次请求发送这个PUBLISH报文。如果DUP标志被设置为1,表示这可能是一个早前报文请求的重发。
客户端或服务端请求重发一个PUBLISH报文时,必须将DUP标志设置为1。对于QoS 0的消息,DUP标志必须设置为0。
服务端发送PUBLISH报文给订阅者时,收到(入站)的PUBLISH报文的DUP标志的值不会被传播。发送(出站)的PUBLISH报文与收到(入站)的PUBLISH报文中的DUP标志是独立设置的,它的值必须单独的根据发送(出站)的PUBLISH报文是否是一个重发来确定。
Message Expiry Interval
消息过期间隔(Message Expiry Interval)标识符。
跟随其后的是四字节整数表示的消息过期间隔(Message Expiry Interval)。
如果消息过期间隔存在,四字节整数表示以秒为单位的应用消息(Application Message)生命周期。如果消息过期间隔(Message Expiry Interval)已过期,服务端还没开始向匹配的订阅者交付该消息,则服务端必须删除该订阅者的消息副本。
如果消息过期间隔不存在,应用消息不会过期。
服务端发送给客户端的PUBLISH报文中必须包含消息过期间隔,值为接收时间减去消息在服务端的等待时间。
时间敏感性
对于QOS=1或2时,分别有2个和4个报文交互,那么这些报文需要在多长时间内发送完成呢?一旦没有在规定的时间内完成,需要怎么处理呢?