MQTT(Message Queuing Telemetry Transport)是一种极其轻量级的发布/订阅消息传输协议,专为低带宽、高延迟、不可靠网络环境下的物联网设备通信而设计。要真正深入理解它,我们需要从它的诞生背景讲起,贯穿其核心设计思想、开源生态,最后深入其工作机制。
一、设计背景:为什么需要 MQTT?
1999 年,IBM 的 Andy Stanford-Clark 和 Arlen Nipper 为了连接石油管道上的传感器与卫星链路,创造了 MQTT。当时的网络环境极其苛刻:
- 带宽极低:卫星通信费用高昂,每条消息都要尽可能小。
- 网络不稳定:物理环境导致频繁断连。
- 设备资源受限:传感器端的计算、存储、电量都非常有限。
- 一对多通信需求:一个传感器数据需要被多个后端系统消费。
当时的企业级协议(如 CORBA、DCOM)或新兴的 HTTP 都太重了:协议头冗长,文本解析复杂,且采用同步的请求/响应模式,无法适应这种场景。
因此,MQTT 从诞生之初就目标明确:为受限设备和不可靠网络提供一种极简、可靠、支持异步的"数据管道"。这正是 MQTT 后来成为物联网(IoT)事实标准协议的根本原因。
二、核心设计思想:贯穿协议的灵魂
MQTT 的整个协议设计可以用几个关键词概括:极简、解耦、可靠、普适。
1. 极简主义 ------ 最小的传输开销
- 二进制协议头:最小只有 2 个字节,相比 HTTP 的文本头(动辄数百字节),优势巨大。
- 精简的方法集:只提供 CONNECT、PUBLISH、SUBSCRIBE、UNSUBSCRIBE、PINGREQ、DISCONNECT 等少量方法,没有复杂的资源操作。
- 无元数据:主题(Topic)即路由 。消息本身不携带目标地址、数据类型等任何额外描述,所有路由和上下文信息完全由主题名承载。这极大地压缩了消息大小。
2. 彻底的空间、时间、同步解耦
这是发布/订阅模式的核心优势:
- 空间解耦:发布者完全不知道订阅者的地址、数量、甚至存在与否。
- 时间解耦 :发布者和订阅者无需同时在线。这正是通过会话(Session)和消息持久化实现的。
- 同步解耦:发布与消费操作完全异步,互不阻塞。
这种解耦让系统具有极高的灵活性和可扩展性。
3. 面向不可靠网络的可靠性设计
MQTT 的可靠性并非"强一致",而是"尽力而为,按需确认",通过三个服务质量(QoS)等级来权衡可靠性与开销:
- QoS 0:至多一次。消息发完即忘,不重试,不确认。
- QoS 1:至少一次。保证送达,但可能重复。
- QoS 2:恰好一次。通过四次握手,确保消息不丢不重。
值得注意的是,QoS 设计是客户端到 Broker,与 Broker 到客户端相互独立的。发布端用 QoS 1 发布,订阅端可以用 QoS 0 接收,反之亦然。
4. 双向连接的持久化与心跳
- 持久连接:MQTT 基于 TCP 长连接,避免了频繁握手开销。
- 遗嘱消息(Will Message):客户端连接时可预设一条遗嘱消息。一旦连接异常断开,Broker 会自动将此消息发布给订阅者,通知"该设备已离线"。这是处理设备不可靠断线的核心机制。
- 心跳保活(Keep Alive):允许在没有业务数据时,以极低的 PINGREQ/PINGRESP 交互维持连接,并能快速发现断线。
5. 终端能力对等与对称设计
在 MQTT 眼中,"设备"和"云端应用"是对等的客户端。一个手机 App 既可以作为发布者上报数据,也可以作为订阅者接收命令。这种对称性极大地简化了双向通信的模型,使其不仅能做"数据采集"(南向),也能做"指令下发"(北向)。
三、开源情况:生态驱动标准
MQTT 的强大,很大程度上源于其蓬勃的开源生态。
核心 Broker 实现
- Mosquitto:Eclipse 基金会旗下的 C 语言实现,极其轻量,是入门、嵌入式和边缘侧部署的首选。
- EMQX:当今最活跃、功能最全的分布式开源 MQTT Broker(Erlang/Elixir 编写)。支持千万级并发连接、规则引擎、数据桥接、多协议接入等企业级特性。有开源的 Community 版和商业的 Enterprise 版。
- NanoMQ:同样是 EMQ 开发,专为边缘端和多核嵌入式系统设计的轻量级 Broker,基于 NNG 异步 IO。
- VerneMQ:基于 Erlang/OTP 的分布式 Broker,以高可用和水平扩展为目标。
- HiveMQ Community Edition:商业版 HiveMQ 的开源版本,基于 Java,强调企业级扩展性。
客户端库
涵盖几乎所有编程语言:
- Eclipse Paho:Eclipse 基金会的官方客户端库系列,覆盖 C、Java、Python、JavaScript、Go、嵌入式 C++ 等,是功能最标准、最基础的参考实现。
- MQTT.js:Node.js 和浏览器端最流行的库,同时支持 WebSocket。
- paho-mqtt:Python 生态标杆。
协议演进与标准化
MQTT 原本是 IBM 的私有协议,2011 年捐赠给 OASIS 开放标准组织,成为 OASIS 标准。
- MQTT v3.1.1(2014 年):成为 ISO/IEC 20922 国际标准,也是目前最普及的版本。
- MQTT v5.0(2019 年):做了重大升级,增加了原因码、属性字段、共享订阅、主题别名、会话过期时间等,大幅提升可扩展性和面向云原生的能力。如今主流 Broker 均已支持。
四、工作机制:深入协议细节
让我们深入到 MQTT 的协议流、数据结构与关键技术内部。
1. 协议报文结构(极简的二进制头)
每个 MQTT 控制报文包含三部分,这正是其高效率的根源:
css
+-----------+---------+-------------------+
| Fixed Header | Variable Header | Payload |
+-----------+---------+-------------------+
- 固定头(2-5 字节) :必有 。
- 控制报文类型(4 bit):如 CONNECT (1), PUBLISH (3), PINGREQ (12) 等。
- 特定标志位(4 bit):仅 PUBLISH 报文使用,包含 DUP 标志、QoS 等级、RETAIN 标志。
- 剩余长度(1-4 字节):这是"变长编码",每个字节最高位指示后面是否还有长度字节。这使得小消息体积得到极致压缩。
- 可变头:部分报文有,内容取决于报文类型。例如 CONNECT 包含协议名/版本、连接标志、保活时间;PUBLISH 包含主题名和报文标识符(QoS>0 时)。
- 有效载荷:部分报文有,如 CONNECT 可携带客户端 ID、遗嘱主题/消息、用户名/密码;PUBLISH 就是真正的应用数据。
2. 连接与会话管理
MQTT 连接基于一条 TCP 连接,但概念上包含两层:传输层的连接 和应用层的会话。
会话(Session)
会话是 Broker 为客户端维持的一系列状态:已订阅的主题集、未消费的 QoS 1/2 消息。这是实现"时间解耦"的核心。
- Clean Session = true(v3.1.1)或 Clean Start = true(v5.0):每次连接时丢弃旧的会话状态,创建一个全新的临时会话。断开即删除所有订阅和未消费消息。适合只发不守的设备。
- Clean Session = false / Clean Start = false :创建或复用持久会话。Broker 会保存订阅和所有未完成的消息。当客户端因断线重连后,所有离线期间抵达的消息会立即推送。这是功耗和可靠性敏感设备的必选配置。v5.0 还允许独立设置"会话过期时间",未过期前断连,会话依然保持。
遗嘱消息(Will Message)
在 CONNECT 报文中预设主题、QoS、保留标志和消息体。一旦 Broker 检测到连接异常断开(没收到 DISCONNECT),就会立即发布这条遗嘱。这解决了"设备静默故障"的感知难题。
3. 消息发布与路由
基于 Topic 的灵活路由
Topic 是 UTF-8 字符串,以 / 分层,如 building/floor1/sensor/temperature。Broker 根据主题名进行逐级匹配,而不解析消息内容。
通配符(Wildcards)
- 单层通配符
+:匹配一层。+/temperature可匹配floor1/temperature,不匹配floor1/roomA/temperature。 - 多层通配符
#:匹配所有层级,只能 位于最后。building/#匹配building下的所有主题。
保留消息(Retained Message)
PUBLISH 中设置 RETAIN 标志为 1 后,Broker 会为该主题存储最后一条保留消息。之后任何新的订阅者,会立即收到该消息,确保其能获取最新状态(如固件版本、配置参数),而不必等待下一次发布。
4. 服务质量(QoS)实现细节
这是 MQTT 可靠性设计的精华,是两个客户端之间的协议,需要分段理解:
-
QoS 0:至多一次 发送端仅发送一次 PUBLISH。不缓存,不重试。可能丢失。
-
QoS 1:至少一次(握手机制) 发送端发送 PUBLISH 报文(携带报文标识符),缓存在本地,直到收到接收端的 PUBACK 确认。如果超时未收到 PUBACK,则重发相同报文标识符的 PUBLISH 且设置 DUP 标志。接收端收到 DUP 消息会根据报文标识符去重(但取决于实现,可能导致业务重复)。
-
QoS 2:恰好一次(四次握手) 这是最精巧的部分:
- 发送端 → 接收端:
PUBLISH(存储消息,锁住该报文ID) - 接收端 → 发送端:
PUBREC(表示已收到,但还未释放) - 发送端 → 接收端:
PUBREL(确认可以释放了,不必担心重传) - 接收端 → 发送端:
PUBCOMP(处理完成,双方释放该报文ID)
无论在哪一步发生断线或丢失,双方都能通过重发机制和报文标识符状态机最终达到一致。v5.0 中,若 QoS 2 的 PUBREL 未收到 PUBCOMP,重连后 Broker 必须重发 PUBREL,防止接收端锁死报文ID。
- 发送端 → 接收端:
5. 心跳与连接保持
Keep Alive 值在 CONNECT 时确定。如果在 1.5 倍 Keep Alive 时间内没有收发任何报文(PINGREQ/PINGRESP 也算),Broker 会判定心跳超时,断开 TCP 连接并触发遗嘱。这让双方都能在无业务数据时以极低成本保活,并能快速发现"半开"连接。
6. MQTT v5.0 的关键增强
- 原因码(Reason Code):几乎所有响应报文都返回原因码,提供细粒度错误信息,如"主题名无效""配额超限"。
- 属性(Properties):报文可携带 Key-Value 元数据,如消息过期时间、内容类型、负载格式指示符、主题别名等,大大增强了可扩展性。
- 共享订阅(Shared Subscription) :主题格式
$share/<group>/topic。同一共享组内的多个订阅者会以某种分发策略(轮询、随机等)均衡消费消息,解决了 MQTT 长期缺乏的"集群消费"问题。 - 主题别名(Topic Alias):用整型 ID 代替长主题名,后续 PUBLISH 只需发 ID,极大地压缩了高频率小消息的体积。
- 请求/响应模式:通过在属性中携带"响应主题"和"关联数据",可以在发布/订阅模型上优雅地实现一对一的请求/响应,避免了客户端预先订阅专属回复主题的复杂性。
总体来看,MQTT 的成功不仅在于它精简的二进制协议,更在于它对物联网通信"不对称性"(设备弱、网络差、云端强)的深刻理解和系统性设计。从遗嘱机制处理故障,到分级 QoS 按需保证可靠,再到 v5.0 引入响应模式和共享订阅向云端生态靠拢,MQTT 展示了一个协议如何用最小的复杂度换取最大范围的应用场景。掌握这些背景和机制,能让你在使用或设计物联网系统时,做出更合适当下的选择。