1.QoS含义
Quality of Service,服务质量
很多时候,使用 MQTT 协议的设备都运行在网络受限的环境下,而只依靠底层的 TCP传输协议,并不能完全保证消息的可靠到达。因此,MQTT 提供了 QoS机制,其核心是设计了多种消息交互机制来提供不同的服务质量,来满足用户在各种场景下对消息可靠性的要求。
MQTT 定义了三个QoS等级,分别为:
- QoS 0 ,最多交付一次。
- QoS 1 ,至少交付一次。
- QoS 2 ,只交付一次。
其中,使用 QoS 0 可能丢失消息,使用 QoS 1 可以保证收到消息,但消息可能重复,使用 QoS 2 可以保证消息既不丢失也不重复。QoS等级从低到高,不仅意味着消息可靠性的提升,也意味着传输复杂程度的提升。
2.服务质量降级
在一个完整的从发布者到订阅者的消息投递流程中,QoS等级是由发布者在 PUBLISH报文中指定的,大部分情况下 Broker向订阅者转发消息时都会维持原始的 QoS不变。不过也有一些例外的情况,根据订阅者的订阅要求,消息的 QoS等级可能会在转发的时候发生降级。
讲到这里,不知道有没有朋友会感到好奇。假如客户端在发布和订阅信息时使用不同级别的QoS,将会发生什么情况呢。如下图所示,假如客户端A发布到主题1的消息是采用QoS = 2,然而客户端B订阅主题1采用QoS = 1。那么服务端该如何来应对这一情况呢?
MQTT-QoS-设置-1
在这种情况下,服务端会使用较低级别来提供服务。如下图所示,虽然A发送到主题1的消息采用QoS为2,但是服务端发送主题1的消息给B时,采用的QoS为1。这是因为B在订阅主题1时采用的QoS为1。
MQTT-QoS-设置-2
下面我们再来看一种情况。
如下图所示,假如客户端A发布主题1消息时使用QoS为0,而客户端B订阅主题1消息时使用QoS为1。
在这种情况下,虽然客户端B订阅主题1消息时QoS为1,但是由于客户端A发送主题1消息时QoS为0,所以服务端发送消息给B的QoS为0。
通过以上两个示例我们可以看到。对于发布和订阅消息的客户端,服务端会主动采用较低级别的QoS来实现消息传输。
3.交互过程
qos0
对于qos1而言,对于client而言,有且仅发一次publish包,对于broker而言,有且仅发一次publish,简而言之,就是仅发一次包,是否收到完全不管,适合那些不是很重要的数据。
QoS 0 是最低的 QoS等级。QoS 0 消息即发即弃,不需要等待确认,不需要存储和重传,因此对于接收方来说,永远都不需要担心收到重复的消息。
qos1
对于qos0而言,这个交互就是多了一次ack的作用,但是会有个问题,尽管我们可以通过确认来保证一定收到客户端或服务器的message,但是我们却不能保证message仅有一次,也就是当client没收到service的puback或者service没有收到client的puback,那么就会一直发送publisher
qos1流程:(publisher -> broker)
1.publisher store msg -> publish ->broker (传递message)
2.broker -> puback -> publisher delete msg (确认传递成功)
注意:
publisher必须保存msg,这样才能在重发
publisher如果在一定时间或socket断开等异常情况,会继续重发msg
为什么 QoS 1 消息会重复?
对于发送方来说,没收到 PUBACK报文分为以下两种情况:
- PUBLISH未到达接收方
- PUBLISH已经到达接收方,接收方的 PUBACK报文还未到达发送方
在第一种情况下,发送方虽然重传了 PUBLISH报文,但是对于接收方来说,实际上仍然仅收到了一次消息。
但是在第二种情况下,在发送方重传时,接收方已经收到过了这个 PUBLISH报文,这就导致接收方将收到重复的消息。
虽然重传时 PUBLISH报文中的DUP标志会被设置为 1 ,用以表示这是一个重传的报文。但是接收方并不能因此假定自己曾经接收过这个消息,仍然需要将其视作一个全新的消息。这是因为对于接收方来说,可能存在以下两种情况:
第一种情况,发送方由于没有收到 PUBACK报文而重传了PUBLISH报文。此时,接收方收到的前后两个 PUBLISH报文使用了相同的PacketID,并且第二个 PUBLISH报文的 DUP标志为 1 ,此时它确实是一个重复的消息。
第二种情况,第一个 PUBLISH报文已经完成了投递, 1024 这个 PacketID 重新变为可用状态。发送方使用这个 PacketID发送了一个全新的 PUBLISH报文,但这一次报文未能到达对端,所以发送方后续重传了这个 PUBLISH报文。这就使得虽然接收方收到的第二个 PUBLISH报文同样是相同的 PacketID,并且 DUP为 1 ,但确实是一个全新的消息。
由于我们无法区分这两种情况,所以只能让接收方将这些 PUBLISH报文都当作全新的消息来处理。因此当我们使用 QoS 1 时,消息的重复在协议层面上是无法避免的。
甚至在比较极端的情况下,例如 Broker从发布方收到了重复的 PUBLISH报文,而在将这些报文转发给订阅方的过程中,再次发生重传,这将导致订阅方最终收到更多的重复消息。
在下图表示的例子中,虽然发布者的本意只是发布一条消息,但对接收方来说,最终却收到了三条相同的消息:
以上,就是 QoS 1 保证消息到达带来的副作用。
qos2
对于qos1而言,qos2可以实现仅仅接受一次message,其主要原理(对于publisher而言),
publisher和broker进行了缓存,其中publisher缓存了message和msgID,而broker缓存了msgID,两方都做记录所以可以保证消息不重复,但是由于记录是需要删除的,这个删除流程同样多了一倍
qos2流程:(publisher -> broker)
publisher store msg -> publish ->broker -> broker store msgID(传递message)
broker -> puberc (确认传递成功)
publisher -> pubrel -> broker delete msgID (告诉broker删除msgID)
broker -> pubcomp -> publisher delete msg (告诉publisher删除msg)
- publisher保存消息
- publisher发送publish报文给broker ,broker 保存msgID,并且进入一个状态,之后不管来了几个这个msgid的消息,都认为是重复的,丢弃。
- broker收到publish的QoS2消息之后,马上回复一个pubrec给发送端。当发送方收到
PUBREC报文,即可确认broker已经收到了PUBLISH报文,发送方将不再需要重传这个报文,并且也不能再重传这个报文。 - publisher 发送一个pubrel 报文给broker
- 当接收方收到 PUBREL 报文,也可以确认在这一次的传输流程中不会再有重传的 PUBLISH报文到达,因此回复PUBCOMP报文表示自己也准备好将当前的msgID作为新消息投递了,投递完删除msgid
- 不管publisher来多少pubrel,都没有messagid的记录,只需要回复pubcomp,不需要在投递
- publisher收到pubcomp销毁该msgid
注意:
第二步,即puberc不可以删除 publisher的msg,因为第三步未必成功,这个时候就需要第一步提醒第二步继续发,而提醒必须要msgID
4.注意事项
QoS=1通讯时的注意事项
如想在MQTT通讯中实现服务质量等级为1级(QoS=1),我们要分别对消息的发布端课接收端进行相应的设置。以下列表中的内容是具体需要采取的措施。
- 接收端连接服务端时cleanSession设置为false
- 接收端订阅主题时QoS=1
- 发布端发布消息时QoS=1
QoS=2通讯时的注意事项
如想在MQTT通讯中实现服务质量等级为2级(QoS=2),我们要分别对消息的发布端和接收端进行相应的设置。以下列表中的内容是具体需要采取的措施。
- 接收端连接服务端时cleanSession设置为false
- 接收端订阅主题时QoS=2
- 发布端发布消息时QoS=2