# MQTT QoS详解

2,099 阅读5分钟

表1:QoS(摘自协议文档)

QoS valuebit 2bit 1Description
000At most once / Fire and Forget / <=1
101At least once / Acknowledged delivery / >=1
210Exactly once / Assured delivery / =1
311Reserved

QoS是Quality of Service的缩写,表示服务质量,MQTT把QoS划分为三级,对应协议值为0、1、2,服务质量依次变高,2代表的服务质量是最好的。MQTT的QoS应用在发送消息的过程中。

QoS0表示消息最多发送一次,发送后消息就被丢弃。

QoS1表示消息至少发送一次,可以保证消息被接收方收到,但是会存在接收方收到重复消息的情况。

QoS2表示消息发送成功且只发送一次,这是一个有且只有的概念,可以保证消息发送成功,且接收方只消费一次消息。

具体到程序实现上,QoS发生于传输通道的建立,作用于通道数据的传输。

这个过程中会涉及几个概念,对其进行命名会便于讲解:

表二:概念命名

概念命名
消息M
消息发送方S
消息接收方R

要弄明白这一过程,还要知道消息的类型:

表三:消息类型(摘自协议文档)

MnemonicEnumerationDescription
Reserved0Reserved
CONNECT1Client request to connect to Server
CONNACK2Connect Acknowledgment
PUBLISH3Publish message
PUBACK4Publish Acknowledgment
PUBREC5Publish Received (assured delivery part 1)
PUBREL6Publish Release (assured delivery part 2)
PUBCOMP7Publish Complete (assured delivery part 3)
SUBSCRIBE8Client Subscribe request
SUBACK9Subscribe Acknowledgment
UNSUBSCRIBE10Client Unsubscribe request
UNSUBACK11Unsubscribe Acknowledgment
PINGREQ12PING Request
PINGRESP13PING Response
DISCONNECT14Client is Disconnecting
Reserved15Reserved

消息的类型不同,用处是不一样的。CONNECT/CONNACK是publisher/subscriber和broker建立连接是用到的消息的类型,PUBLISH/PUBACK/PUBREC/PUBREL/PUBCOMP是发送消息时用到的类型,SUBSCRIBE/SUBACK是订阅消息用到的类型,等等。

发送消息这一过程存在两种情况:

  1. publisher向broker发送消息
  2. broker转发消息给subscriber

所以消息发送方可能是publisher也可能是broker,消息接收方可能是subscriber也可能是broker。以上两种情况只是发送双发的差异,消息的传输过程是完全一样的。

QoS0

“最多发送一次消息”。这种情况最为简单,消息发送方把消息丢出去,过程就结束了。整个过程只用到PUBLISH类型的消息。

S ---M(PUBLISH)---> R

可能的结果:

  1. 消息接收方收到了消息
  2. 消息接收方没有收到消息

消息发送方只负责发消息,并且只发一次,不关心消息有没有被收到。

消息接收方只负责收消息,其它也不关心。

QoS1

“至少发送一次消息”。这个过程用到两种消息类型,分别是PUBLISH和PUBACK。

首先我们假设网络传输是可靠的,程序是没有BUG的,也就是理想状态下的传输过程:

S ---M(PUBLISH)---> R
S <---M(PUBACK)--- R

现实中,网络传输是不可靠的,程序是有BUG的,所以,上面的传输过程会存在下面的问题:

  1. M(PUBLISH)发送失败
  2. M(PUBACK)发送失败

对消息发送方的影响: 无论是哪个发送失败,都会导致S重复发送M消息。

对消息接收方的影响: M(PUBLISH)发送失败对消息接收方没有影响,反正S会保证给“我”消息。 M(PUBACK)发送失败会导致消息接收方收到重复的消息。

相较于QoS0增加的开销:

  1. M(PUBLISH)重复发送,会占用带宽
  2. M(PUBACK)消息确认机制,会占用带宽
  3. 消息发送方需要增加消息超时重发机制,增加程序复杂度,同时带来存储资源开销

QoS2

“有且只有一次,确保消息发送成功且只被接收方处理一次”。首先还是假设在完美情况下,看一下协议定义的交互过程,一共涉及四种类型的消息。

S ---M(PUBLISH)---> R
S <---M(PUBREC)--- R
S ---M(PUBREL)---> R
S <---M(PUBCOMP)--- R

基于网络传输不确定和程序总会存在BUG的事实,以上过程存在的问题:

  1. M(PUBLISH)消息发送失败
  2. M(PUBREC)消息发送失败
  3. M(PUBREL)消息发送失败
  4. M(PUBCOMP)消息发送失败

问题1和问题2 会导致S不停的向R发送重复消息。为了满足有且只有一次的需求,需要设计一个去重机制:

R创建两个队列,队列A用于保存收到的M(PUBLISH),队列B用于保存收到的M消息的ID(可以在M(PUBLISH)获取)。当R收到M(PUBLISH)时,首先去队列A检查是否存在这个消息,如果不存在则保存,否则直接丢弃,然后向S发送M(PUBREC)消息。当S收到PUBREC消息后,则停止发送M(PUBLISH)消息,并在发送消息队里删除消息M。接下来S会向R发送M(PUBREL)消息。

问题3和问题4 会导致S不停的向R发送M(PUBREL)消息

R收到M(PUBREL)消息后,会取出ID,并在队列B中进行检索,如果存在,则从队列A中取出对应的消息M进行消费,并从队列A和B中删除;如果不存在,则直接丢弃M(PUBREL)消息。接着,向S发送M(PUBCOMP)消息。到这里消息M在R中的处理就完成了,还剩下一种情况会导致S对消息M的处理没有完全完成,这种情况就是:M(PUBCOMP)消息发送失败了。

接下来的逻辑是这样:S没有收到M(PUBCOMP)消息,会继续发送M(PUBREL)消息。R收到M(PUBREL)消息后,在队列B找不到消息ID,会直接发送M(PUBCOMP)消息。总有一次M(PUBCOMP)消息可以发送成功。这样S对消息M的处理也完成了。

至此,消息M处理完成,并且协议上层应用收到且只收到一条M消息 。

重传机制和去重机制:

上面过程中的重传机制只发生在消息发送方,消息接收方只是被动接收消息,然后给与回应。

上面过程中的去重机制只发生在消息接收方。

相较于QoS1增加的开销:

  1. 一次消息链路变长了,M(PUBREL)重复发送、M(PUBREC)和M(PUBCOMP)占用带宽
  2. 消息接收方的去重机制增加代码复杂度,同时带来存储资源开销

后记

QoS1的配置能不能实现“有且只有一次,保证发送成功且只消费一次”?

相比较之下,QoS1存在的问题是消息接收方会收到重复的消息,可以给其增加去重机制,就可以在QoS1的配置下做到“有且只有一次”。但是,考虑到计算机的资源(存储和算力)是有限的,程序实现起来会有一些问题。

去重机制需要有一个队列A来记录已经处理过的消息,在QoS1只有两种消息类型的配置下,消息接收方不知道消息发送方什么是否不在发送消息M,所以队里A无法执行删除操作,这样一来,队列A的内容越来越多,会导致程序效率降低,甚至好近存储和算力。