一文了解 MQTT

1,381 阅读7分钟

MQTT 是什么

MQTT 是 Message Queuing Telemetry Transport 的简称,即消息队列遥测传输协议 ,它是一种基于发布 / 订阅模式的轻量级物联网消息传输协议。在 Android 开发中,MQTT 多被用来实现长连接、消息推送等功能。

MQTT 与 WebSocket 和 SSE 的区别是什么

除了 MQTT 之外,我们还可以使用 WebSocket 来实现长连接,以及使用 SSE 来进行数据推送。那么它们之间的区别是什么呢?如下图所示:

image.png

可以看到,SSE 是基于HTTP的协议,它只能从服务端单向的发送流式数据到客户端,通过这种方式来实现数据推送的功能;WebSocket 则是通过 TCP 全双工来实现消息推送;而 MQTT采用的是订阅模式,给订阅了指定主题的客户端推送消息。

为什么需要使用 MQTT

MQTT 最初是为了利用卫星通讯监控输油管道所开发的协议。设计之初就考虑到了低带宽、高延迟或不可靠的网络环境等各种因素。因此,MQTT 具备了轻量、传输可靠、低功耗等特点,非常适合物联网设备的交互、后台推送服务处理推送消息。

MQTT 与 Websocket 的具体具体区别如下,更多可以看 MQTT 与 WebSocket:关键差异与应用场景

image.png

MQTT 的订阅机制

image.png

如上图所示,MQTT提供订阅主题的方式来推送消息。在MQTT中,主题主要分为两种,系统主题和普通主题。

  • 系统主题

系统主题主要以 $SYS/ 开头,主要发送的是服务器系统相关的通知

  • 普通主题

普通主题则是业务直接来约定格式,比如说:

login/user/000001 订阅用户id 000001 的登录,/ 用来分层

MQTT也支持使用通配符来订阅,比如单层通配符 + ,以及多层通配符 #

单层通配符使用示例如下

+ 有效
sensor/+ 有效
sensor/+/temperature 有效
sensor+ 无效(没有占据整个层级)

多层通配符使用示例如下

有效,匹配所有主题
sensor/# 有效
sensor/bedroom# 无效(没有占据整个层级)
sensor/#/temperature 无效(不是主题最后一个字符)

订阅需要注意的点:

  1. MQTT 协议规定主题的长度为两个字节,因此主题最多可包含 65,535 个字符
  2. 主题层级建议为 7 个以内

MQTT的报文

为了轻量化,MQTT的报文格式非常简单。它由三部分组成:固定报头、可变报头、消息体(也叫有效负载)。其中只有固定报头是一定存在的,可变报头、消息体不一定存在。

固定报头

固定报头的格式如下图所示:

image.png

image.png

  • Qos,服务质量,占两个bit

数据通信的过程中,有的消息很重要,不可以丢失;有的消息不重要,丢了也没关系。所以在 MQTT 中可以配置 QoS,给不同重要的消息不同的服务质量。MQTT 协议有三种服务质量级别:QoS = 0:最多发一次,消息可能会丢失;QoS = 1:最少发一次,消息不会丢失,但是可能重复;QoS = 2:保证收一次,消息不丢失,不重复。

  • DUP,是否是重传的报文,占一个 bit

当消息丢失时,MQTT会重发这一条消息,这时这条消息的报文的 DUP 位会被置为 1,表示这是一条重传的消息。

  • RET,是否是保留消息,占一个bit

保留消息,当有新的订阅者时,发送之前发送的最新的消息。类似于livedata 或者 stateflow的重放机制。

  • 剩余长度,指可变报头加上消息体的大小。剩余长度是可变的,大小为 1-4个字节

剩余长度:可变报头 + 消息体的大小。每个字节中的低 7 位用于编码数据,最高的有效位用于指示是否还有更多的字节。因此理论上报文最大大小为 2 ^ 28 = 256MB。这只是理论上,具体限制需要看MQTT服务器,比如说阿里云服务端则规定了最大报文大小为128 KB字节,发送报文时,消息体大小不超过64 KB的限制。

注意:固定报头虽然名字带上了固定,但是实际上并不是固定的。报文的剩余长度这一部分就是可变的

可变报头和消息体

可变报头和消息体的内容取决于具体的报文类型。这里以发送消息的报文 PUBLISH 为例,它的可变报头格式如下图所示。需要注意,报文标识符只在 Qos = 1 或者 2 时才会存在。

image.png

消息体顾名思义就是消息内容。一个完整的 Qos 为 0 的 PUBLISH 报文如下图所示。图片来源MQTT 5.0 报文解析 02:PUBLISH 与 PUBACK

image.png

遗嘱消息

MQTT遗嘱是一种机制,允许客户端在「活着」的时候设置并发送遗嘱消息,以便在客户端意外断线时由服务端公布。意外断线指的是当客户端在没有发送 DISCONNECT 报文的情况下失去了心跳信号,这通常发生在网络故障或电池耗尽等情况下。此时,服务端会察觉到客户端的异常断开,并将客户端的遗嘱消息发布出来。然而,如果客户端正常断开连接并发送了 DISCONNECT 报文,遗嘱则不会启动,服务端也不会发布客户端的遗嘱消息。

推送原理

前文已经提及,MQTT 是凭借 Qos(服务质量等级)来提供多种服务保障的。接下来,我们就详细讲解一下这些保障的实现原理。

  • QoS = 0:最多发一次,消息可能会丢失

image.png

  • QoS = 1:最少发一次,报文不会丢失,但是可能重复

image.png

为什么 Qos = 1 时,消息会重复?

对于发送者,由于无法确定是 PUBLISH 报文 还是 PUBACK 报文发送失败,因此会重新处理这个新发送的 PUBLISH 报文。因此会发送多个 PUBLISH 报文 重试

对于接收者,一般情况下可以通过 PacketID(报文ID) + DUP(重发标识) 来确定当前消息是否是重发的消息。但是由于 PacketID 只分配了两个 Byte,最大值为 65536 ,因此 PacketID 存在复用的情况(复用说明,看MQTT标准)。为了避免这种情况下,把新报文当作重复报文抛弃,Qos 为 1时,接收者不会根据PacketID(报文ID) + DUP(重发标识)来判断当前消息是否是重发消息,而是都会处理,因此消息会重复。

  • QoS = 2:保证收一次,消息不丢失,不重复

image.png

对于QoS = 2,MQTT会确保 PUBREL 报文之前的相同的 PacketID 报文为重复报文;之后的,相同的 PacketID 才被视为新报文来确保不会出现上述的问题,进而保证只收一次消息。

由于发布者和订阅者可以使用不同的 Qos,因此还有一种服务降级的情况需要了解下。

  • 如果订阅者的 Qos 比发布者的小,使用订阅者的 Qos 通知,效果如下图所示:

image.png

  • 如果订阅者的 Qos 比发布者的大,使用发布者的 Qos 通知,效果如下图所示:

image.png

服务降级其实可以简单理解成,up上传了 2k 的视频。我们粉丝(订阅者)可以选择看720p、1080p的,但是无法看4k的,因为up上传的最大就是2k。

参考