MQTT 协议:从发布/订阅到 QoS,一篇讲清楚
它解决什么问题
传统的 HTTP 是请求-响应模型:客户端主动问,服务端被动答。这在浏览器场景没问题,但换到物联网就不太合适了——成千上万个传感器需要持续上报数据,设备端算力和带宽都很有限,HTTP 的头部开销和连接管理成本太高。
MQTT(Message Queuing Telemetry Transport)就是为这种场景设计的。它是一个轻量级的消息传输协议,基于 TCP,最小报文只有 2 字节。OASIS 标准组织维护,当前主流版本是 MQTT 3.1.1 和 MQTT 5.0。
发布/订阅模型
MQTT 的核心是发布/订阅(Pub/Sub)模式,整个通信涉及三个角色:
- Publisher(发布者):发消息的一方,比如温度传感器
- Subscriber(订阅者):收消息的一方,比如监控面板
- Broker(代理):中间人,负责接收消息并转发给订阅者
发布者和订阅者之间完全解耦——它们不需要知道对方的存在,甚至不需要同时在线。所有消息都通过 Broker 中转。
常见的开源 Broker 有 Mosquitto(轻量,适合开发和小规模部署)、EMQX(Erlang 写的,主打大规模集群)、NanoMQ(边缘计算场景)。
主题(Topic)
消息靠主题来路由。主题是一个 UTF-8 字符串,用 / 分层,类似文件路径:
home/living-room/temperature
home/bedroom/humidity
factory/line-1/machine-3/status
订阅时可以用通配符:
+匹配单层:home/+/temperature匹配home/living-room/temperature和home/bedroom/temperature,但不匹配home/floor-2/bedroom/temperature#匹配多层(只能放末尾):home/#匹配home下所有层级的所有主题
主题不需要预先创建,发布者往一个主题发消息,Broker 自动处理路由。
QoS 等级
MQTT 定义了三个服务质量等级,用来在可靠性和性能之间做取舍:
| QoS | 名称 | 语义 | 报文交互 |
|---|---|---|---|
| 0 | At most once | 最多送达一次,可能丢 | PUBLISH(单次,不确认) |
| 1 | At least once | 至少送达一次,可能重复 | PUBLISH → PUBACK |
| 2 | Exactly once | 恰好送达一次 | PUBLISH → PUBREC → PUBREL → PUBCOMP |
QoS 0 最快、开销最小,适合高频上报且偶尔丢一条无所谓的场景(比如每秒一次的温度数据)。QoS 2 最可靠但最慢,四步握手的开销不小,只在关键指令(比如远程开关阀门)时才值得用。
实际选型中 QoS 1 用得最多——重复消息在业务层做幂等处理,比 QoS 2 的四步握手划算。
连接与保活
MQTT 客户端通过 TCP 连接到 Broker,第一步是发 CONNECT 报文:
CONNECT 报文关键字段:
├── Client ID → 客户端唯一标识
├── Clean Session → 是否清除上次的会话状态
├── Keep Alive → 心跳间隔(秒)
├── Username → 可选,用于认证
├── Password → 可选,用于认证
└── Will Topic/Msg → 可选,遗嘱消息
Broker 收到后回 CONNACK,连接建立。
Keep Alive 是心跳机制:客户端在这个间隔内如果没有发送任何报文,就必须发一个 PINGREQ,Broker 回 PINGRESP。如果 Broker 在 1.5 倍 Keep Alive 时间内没收到任何报文,就认为客户端掉线。
遗嘱消息(Last Will)
这是 MQTT 比较巧妙的一个设计。客户端在 CONNECT 时可以预设一条”遗嘱”——指定一个主题和消息内容。如果客户端异常断开(没有发 DISCONNECT 就断了 TCP),Broker 会自动把这条遗嘱消息发布出去。
典型用法是做在线状态通知:
Will Topic: device/sensor-42/status
Will Message: offline
Will QoS: 1
Will Retain: true
设备正常工作时定期往 device/sensor-42/status 发 online。一旦设备断网或崩溃,Broker 自动发布 offline 到同一主题。监控系统订阅了这个主题,就能实时感知设备离线。
保留消息(Retained Message)
普通的 MQTT 消息是”阅后即焚”——Broker 转发给当前在线的订阅者就完事了。但如果设置了 Retain 标志,Broker 会保存这条消息。后续有新的客户端订阅这个主题时,会立刻收到最后一条保留消息,不用等到下次发布。
适合发布不频繁但新订阅者需要立刻知道当前值的场景,比如设备的配置信息、当前运行状态。
报文结构
MQTT 的报文设计很紧凑:
固定头(2 字节起)
├── 报文类型(4 bit):CONNECT / PUBLISH / SUBSCRIBE 等共 14 种
├── 标志位(4 bit):DUP / QoS / Retain
└── 剩余长度(1-4 字节):变长编码,最大表示 256 MB
可变头(视报文类型而定)
├── Packet ID(QoS > 0 时才有)
└── 协议特定字段
载荷(Payload)
└── 消息内容
剩余长度用的是变长编码:每个字节的低 7 位是有效数据,最高位是延续标志。1 个字节最大表示 127,2 个字节最大 16383,以此类推,最多 4 字节,上限约 256 MB。
这意味着一条 QoS 0 的短消息,协议头开销可以压到只有 2 字节——对比 HTTP 动辄几百字节的 header,差距很明显。
MQTT 5.0 的几个变化
MQTT 5.0 在 3.1.1 基础上加了不少实用特性:
- 属性系统:报文可以携带键值对属性,比如消息过期时间、响应主题、内容类型
- 共享订阅:多个订阅者组成一个组,消息在组内负载均衡分发(3.1.1 只能广播)
- 原因码:CONNACK、PUBACK 等回复报文带上具体的错误原因,不再只有”成功/失败”
- 主题别名:用一个短整数代替长主题字符串,减少重复传输的带宽消耗
- 流量控制:客户端和 Broker 可以协商同时在途的最大消息数
如果是新项目,直接上 5.0。但要确认 Broker 和客户端库都支持——部分嵌入式 SDK 还停留在 3.1.1。
和其他协议的对比
| MQTT | HTTP | WebSocket | CoAP | |
|---|---|---|---|---|
| 传输层 | TCP | TCP | TCP | UDP |
| 模型 | Pub/Sub | 请求/响应 | 双向流 | 请求/响应 |
| 最小头开销 | 2 字节 | 几百字节 | 2-14 字节 | 4 字节 |
| 适用场景 | IoT 设备通信 | Web 应用 | 实时 Web | 受限网络的 IoT |
MQTT 和 CoAP 经常被拿来比。简单说:MQTT 基于 TCP,可靠性由协议层保证,适合网络相对稳定的场景;CoAP 基于 UDP,更轻量,适合网络极差、丢包率高的受限环境(比如 NB-IoT)。