一、什么是MQTT?
MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅模式消息传输协议,专为低带宽、高延迟或不稳定网络环境设计,广泛应用于物联网(IoT)、移动应用和嵌入式系统。其核心目标是实现高效、可靠且低功耗的通信。
二、MQTT与消息队列的区别
在学习MQTT的过程中发现与消息队列的行为和特性都非常接近,比如都是用发布/订阅模式。
但实际上:
- 消息队列主要用于服务端应用之间的消息存储与转发。特点是数据量大但客户端数量少。
- MQTT是一种消息传输协议,主要用于物联网设备之间的消息传递。特点是海量的设备接入、管理与消息传输。
三、MQTT基础概念
1、MQTT发布/订阅
MQTT与很多消息队列一样,MQTT也采用发布/订阅模式,通过broker来实现消息发布者和接收者解耦。broker负责消息的路由和分发。
MQTT在客户端订阅或发布时即自动创建主题,开发者无需关心主题的创建。所以broker也无法预知某一个主题后续是否有订阅者,只能将消息转发给当前的订阅者(除非显式使用保留消息或持久会话)。保留消息是为“未来订阅者”提供最后已知状态的机制,但这是通过发布者主动设置的策略,而非 Broker 的预知能力。
MQTT使用唯一的ClientId来区分客户端。
2、MQTT的Topic
MQTT的topic类似于URL路径,使用 / 进行分层,分层可以代表IOT系统的不同方面,比如设备位置、传感器类型等。
比如:
可以订阅 device/{deviceId}/temperature的topic,获取设备id为123或456上报的温度。
- device/123/temperature
- device/456/temperature
当然MQTT的topic支持通配符,可以有效的增强协议的灵活性。
单层通配符:+ 号是用于单层的通配符。
比如想获取设备id为123和456上报的温度,可以将订阅的topic设置为 devce/+/temperature
多层通配符:# 号用于匹配主题中任意层级的统配符。(必须是主题最后一个字符)
比如想获取设备123所有的上报信息,可以将topic设置为 devce/123/#
3、MQTT QoS
MQTT中的QoS指的是发布者与订阅者之间消息传递的保证级别:
- QoS0 最多交付一次;使用QoS0可能丢失消息
- QoS1 至少交付一次;使用QoS1可以保证收到消息,但消息可能重复
- QoS2 只交付一次;使用QoS2可以保证消息既不丢失也不重复
QoS0 的缺点是可能会丢失消息,优点是投递效率高。所以我们如果传输一些高频且不那么重要的数据可以使用QoS0
QoS1 可以保证消息到达,但可能会导致消息重复。适合传输一些较为重要的数据,同事需要能够处理消息的重复,或者能够允许消息的重复。
可以在报文的payload增加一个时间戳或者单调递增的计数判断是否是一个新的消息。
QoS2 既可以保证消息到达,也可以保证消息不回重复,但传输成本高。
4、MQTT会话
在实际中设备往往可能因为网络等原因频繁地断开连接。如果重连都以全新的上下文建立连接会导致以下问题:
- 客户端重连后必须重新订阅主题才能继续接受消息无法接受离线期间的消息
- QoS1和QoS2的服务质量无法得到保证
- 为了避免这些问题,MQTT协议设计了会话机制。
MQTT会话概念
会话本质就是将客户端与服务端连接上下文给储存下来。客户端和服务器可以通过这些上下文数据恢复到网络断开前的状态。
MQTT5中提供了两个字段控制会话的生命周期:
- clean start
- 设置为0 ,如果服务端存在Client ID关联的会话,那么必须使用这个会话来恢复通信
- 设置为1,服务端与客户端必须丢弃任何已存在的会话,并开启一个新的会话
- seesion expiry interval
- 默认或者设置为0,表示会话将在网络连接断开时立即结束
- 设置的值大于0时,表示会话将在网络连接断开的多少秒后过期
- 设置为0xFFFFFFFF,表示会话永不过期
clean start=0 与 session expiry interval=0 的组合会导致会话在断开后立即失效。客户端首次连接时创建会话,但重连时无法恢复,必须重新建立新会话。
四、MQTT 核心概念
1、保留消息
发送消息时,可以通过retained=true将消息设置为保留消息。MQTT服务器会为每个Topic保存最新一条的保留消息。当客户端订阅了有保留消息的主题后,即会收到该主题的保留消息。
利用保留消息,新的订阅者能够立即获取最近的状态,而不需要等待无法预期的时间,比如:
- 智能家居设备的状态只有在变更时才会上报,但是控制端需要上线后就能获取到设备的状态
- 传感器的版本号、序列号等不会经常变更的属性,可在上线后发布一条保留消息告知后续的所有订阅者。
2、遗嘱消息
在MQTT中,客户端可以在连接时在服务端中注册一个遗嘱消息。当该客户端意外断开连接,服务端就会向其他订阅了相应主题的客户端发送此遗嘱消息。接受者可以即使的采取相应的行动,发送通知、切换设备等。
什么条件下会发送遗嘱消息?
只要网络连接在服务端没有收到Reason Code为0x00的DISCONNECT报文的情况下关闭,那么服务端都需要发送遗嘱消息。如:I/O错误或者网络故障、客户端在Keep Alive时间内未能通讯。
Will Delay Interval
为了避免短暂的网络中断导致遗嘱消息被频繁且无意义的发送,MQTT5.0为遗嘱消息增加了一个Will Delay Interval属性。如果Will Delay Interval 大于0,并且客户端能够在Will Delay Interval到期前恢复连接,那么就不会发送遗嘱消息。
遗嘱消息与会话
遗嘱消息是服务端会话状态的一部分,当会话结束,遗嘱消息也无法继续单独存在。这就会导致一个问题:
在遗嘱消息延迟发布期间,会话可能过期,也可能Clean Start设置为1 服务端需要丢弃之前的会话
所以为了避免丢失遗嘱,此时服务端必须发布遗嘱消息,即便Will Delay Interval还没有到期。所以最大延迟时间取决于Will Delay Interval与Session Expiry Interval谁先到期。

可以通过设置一个大于 Session Expiry Interval 的 Will Delay Interval,服务端可以以遗嘱消息的形式发出会话过期通知。这对于一些更关心会话过期而不是网络连接中断的应用更加有用。
3、MQTT请求与响应
MQTT的发布订阅机制跟消息队列一样,发送端只能确保消息到达服务端,而无法知晓订阅端是否收到了消息。要想实现请求响应,需要订阅端在收到请求后向响应主题返回响应即可。
响应主题
发送方可以在请求消息中指定一个自己期望的响应主题(Response Topic)。响应方根据请求内容采取适当的操作后,向请求中携带的响应主题发布响应消息。如果请求方订阅了该响应主题,那么就会收到响应。
同时,请求方还可以再请求中携带关联数据 (Correlation Data),响应方必须在响应中将关联数据原封不动地返回,请求方因此可以识别响应所属的原始请求。
4、用户属性
用户属性是自定义属性,允许用户向MQTT消息添加自己的元数据,传输额外的自定义信息以扩充更多应用场景。
用户属性是为了解决MQTT3 的协议扩展性能力较差。
5、主题别名
主题别名是为了减少对发送大量相同主题的消息时对客户端和服务端之间带宽资源的浪费以及服务端每次解析相同主题的计算资源浪费。就是为了降低资源的消耗。
任何主题名都可以使用主题别名缩减为编码长度为2字节的整数。
主题别名生命周期和作用范围
主题别名的值由各端各自维护。生命周期和作用范围仅限于当前连接。断开连接后需要重新建立映射关系。
客户端与服务端会在CONNECT报文和CONNACK报文中完成对可以使用最大主题别名长度的约定。

客户端和服务端会在第一次发布带有主题别名和非空主题名后,建立主题别名和主题的映射关系。主题别名的值不允许为0,同时不能超过约定的最大主题别名长度。

如果使用未设置的主题别名,broker将使用包含原因码(REASON_CODE)为0x82的DISCONNECT报文断开网络连接。
重置别名:只需要发布一条新的带有主题别名和主题名的消息。
6、共享订阅
共享订阅可以使得MQTT服务端可以在使用特定订阅的客户端之间均衡地分配消息负载。这一点与RocketMQ中消费者组的目标类似,都是为了分摊消息处理压力提高消息处理的并发能力。
想使用共享订阅只需要在订阅时使用遵循特定命名规范的主题即可:
$share/{Share Name}/{Topic Filter}:
- $share用于服务端知道这是一个共享订阅主题
- {Share Name} 就是消费者组的名称
- {Topic Filter} 就是我们实际的topic
只有*{Share Name}/{Topic Filter}才能唯一地标识一个共享订阅组。共享订阅与普通订阅互不影响。当某个消息同时与共享订阅和普通订阅匹配时,如果一个客户端既匹配普通订阅又匹配共享订阅,那么这个客户端可能会收到该消息的多个副本。
共享订阅负载均衡策略:
- 随机(Random):在共享订阅组内随机选择一个会话发送消息。
- 轮询 (Round Robin):在共享订阅组内按顺序选择一个会话发送消息
- 哈希 (Hash):基于某个字段的哈希结果分配
- 粘性 (Sticky):在共享订阅组内随机选择一个会话发送消息,此后保持这一选择,直到该会话结束再重复这一过程。
- 本地优先 (Local):随机选择,但优先选择与消息的发布者处于同一节点的会话,如果不存在这样的会话,则退化为普通的随机策略。
实际应用中,消息之间可能存在关联,比如属于同一张图片的多个分片显然不适合分发给多个订阅者。这个时候可以基于Client Id或者Topic的哈希策略来选择会话。
EMQX默认的共享策略为Round Robin
共享订阅使用建议:
- 在共享订阅组内使用相同的QoS
- 合理地设置会话过期时间
未完待续。。。
3085

被折叠的 条评论
为什么被折叠?



