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
For QoS > 1, the message expiry interval dictates how long the retained message is kept.
However, it is important that the Broker avoids sending messages indefnitely for the Clients that
never update their tokens (i.e., the Client connects briefy with a valid token, sends a PUBLISH
packet with the RETAIN fag set to 1 and QoS > 1, disconnects, and never connects again).
Therefore, the Broker use the minimum of the token expiry and message expiry interval to
discard a retained message.
目前我们订阅时的QOS lever为2,发布时的QOS level 为0:
发布的时候Retain标志位为0,表示需要broker保存此topic的消息。
当生产者发布在topic上一条消息,同时在把retained=1。尽管消费者订阅topic是在发布消息之后,但消费者还是能消费到topic最后一条消息。特别注意:MQTT Broker只会为每一个topic保存最近收到的一条retained=true的消息!也就是说,如果MQTT Broker上已经为某个topic保存了一条retained消息,当生产者再次往该topic发布一条新的retained=true的消息,那么MQTT Broker上原来的那条消息会被覆盖。
一个新的消费者订阅多个topic,Broker会检查每个匹配的主题名中是否有retained=1的消息,如果存在最近保留的消息,它必须被发送给这个订阅者。
如果客户端发给服务端的PUBLISH报文的保留标志位: retained= 0
,服务端不能存储这个消息也不能移除或替换任何现存的保留消息。
如果客户端想让MQTT Broker删除某个topic下保存的retained=true消息,唯一的方法是向MQTT Broker发布一条retained=true的空消息。
即使生产者掉线了,消费者一旦上线即刻就能订阅生产者最后一条发布的消息。
特别注意:消息的RETAIN标志位与消息的QoS无关,即无论使用哪种级别的QoS,只要设置了RETAIN标志位为 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个报文交互,那么这些报文需要在多长时间内发送完成呢?一旦没有在规定的时间内完成,需要怎么处理呢?