标签

MQTT

MQTT是Message Queuing Telemetry Transport(消息队列遥测传输)的缩写,是一种轻量级的、基于发布/订阅模式的消息通信协议,最初是由IBM公司在20世纪90年代开发的。MQTT协议专门应用于物联网设备之间的通信,旨在实现物联网设备的低带宽、低功耗和低成本特性。MQTT协议采用客户端/服务器模式,其中客户端可以是传感器、智能设备、移动应用程序等,而服务器则可以是云端服务器或物联网网关。MQTT协议的核心概念是主题(Topic)和消息(Message),客户端可以发布消息到一个或多个主题,也可以订阅一个或多个主题以接收消息。MQTT协议的优点包括:具有低带宽和低功耗特性,适用于各种物联网设备;支持高度灵活的主题订阅机制,可以实现高效的消息传输;支持多种消息质量等级,可以满足不同的应用需求;支持TLS加密和认证机制,可以提高消息传输的安全性。MQTT协议已经被广泛应用于各种物联网场景,如智能家居、智能交通、智能医疗和智能制造等。

MQTT
查看更多相关内容
服务端2026年5月31日 00:26
MQTT 是什么?它的核心特点和工作原理是什么?MQTT 是一种基于 TCP 的轻量级消息协议,最常见于物联网设备、移动推送和实时状态同步。它的核心不是“像 HTTP 一样请求接口”,而是通过 Broker 做发布/订阅:设备把消息发布到主题,其他客户端订阅主题后由 Broker 推送消息。这个模式让设备不必知道彼此地址,也能在弱网、低带宽和大量连接场景下稳定通信。 ## MQTT 为什么适合物联网? 第一个特点是轻量。MQTT 固定头部最小只有 2 字节,比 HTTP 一大串 header 更省流量。对电池供电设备来说,少发一点数据、少建立几次连接,都会影响续航。它还通过 Keep Alive 维持长连接,Broker 可以主动把消息推给客户端,不需要客户端频繁轮询。 第二个特点是发布/订阅。发布者只把消息发到 topic,例如 `factory/line1/motor/temperature`,订阅者通过主题过滤器接收自己关心的消息。Broker 负责连接管理、主题匹配、消息分发和 QoS 状态。这个设计天然支持一对多,比如一台设备上报状态后,监控系统、告警系统和数据存储服务都可以同时收到。 第三个特点是可靠性交给 QoS 分级处理。QoS 0 最快但可能丢,QoS 1 保证至少到达但可能重复,QoS 2 尽量做到恰好一次但成本最高。实际项目里通常不是全部用最高级别,而是遥测用 QoS 0,告警和状态变更用 QoS 1,极少数关键命令才考虑 QoS 2。 MQTT 的工作流程可以概括为四步:客户端 CONNECT 到 Broker,订阅者 SUBSCRIBE 主题,发布者 PUBLISH 消息,Broker 根据订阅关系转发。断线时,Broker 可以通过遗嘱消息通知其他系统;重连时,持久会话可以恢复订阅和未确认消息。它看起来简单,但真正上线时要同时考虑主题设计、认证授权、离线消息上限和消息幂等。 它也不是所有实时通信的默认答案。浏览器前端更常用 WebSocket,服务端内部任务分发可能更适合 Kafka 或 RabbitMQ,MQTT 的强项是大量客户端长连接和主题路由。判断是否使用 MQTT,可以先问三个问题:设备是否经常在线保持连接、消息是否需要按主题推送、网络和功耗是否敏感。如果答案都是否定,HTTP 可能更简单。 ```bash mosquitto_sub -h test.mosquitto.org -t 'levenx/demo' mosquitto_pub -h test.mosquitto.org -t 'levenx/demo' -m 'hello mqtt' ``` 初学者可以用公开测试 Broker 验证协议概念,但不要把它当作生产样板。生产 Broker 要考虑账号隔离、TLS 证书、ACL、限流、日志和监控,还要有备份和升级方案。MQTT 的入门门槛低,真正难的是长期稳定运行。越早把这些工程约束放进设计,后面越少返工。 ## 追问 ### MQTT 和 HTTP 最大区别是什么? HTTP 主要是请求/响应,客户端问一次,服务器答一次。MQTT 是长连接加发布/订阅,Broker 可以在有消息时主动推给订阅者。取舍很明显:配置查询、文件上传、管理后台接口适合 HTTP;设备状态、实时告警和低带宽上报更适合 MQTT。很多系统会混用,别试图用一个协议解决所有问题。 ### Broker 是不是单点? 逻辑上 Broker 是中心节点,所以单机 Broker 确实可能成为单点。生产环境可以用集群、负载均衡和客户端自动重连降低风险。边界在于 MQTT 长连接有会话状态,故障切换不像无状态 HTTP 那么简单。要验证 Broker 高可用,必须实际测试节点宕机、网络抖动和客户端重连后的消息表现。 ### MQTT 基于 TCP,为什么还需要 QoS? TCP 只能保证一条连接上的字节流可靠、有序,不能保证应用消息在断线、重连、Broker 转发和订阅者离线时符合业务预期。MQTT QoS 是应用层的交付语义,用来处理确认、重传和重复问题。踩坑点是以为 TCP 可靠就等于业务可靠,结果设备掉线时消息丢了还不知道。QoS 要和持久会话、离线队列、业务幂等一起看。 ### MQTT 适合传大文件吗? 不适合。MQTT 更适合小消息、高频状态和控制指令,大文件会占用 Broker 内存、网络和队列资源。文件上传、固件下载更适合 HTTP、对象存储或专门的 OTA 通道。实际取舍是:MQTT 可以发文件地址、版本号和下载指令,但不要把固件二进制直接塞进 MQTT payload。 ### 新手接入 MQTT 最容易忽略什么? 最容易忽略主题规范和安全配置。刚开始大家会用 `test/#`、匿名连接和公网 1883 调试,跑通很快,但上线后权限和排障都很痛苦。另一个坑是没有给消息设计唯一 ID,遇到 QoS 1 重复投递时无法去重。把 Client ID、topic、QoS、ACL 和日志字段提前约定好,比后期补救省很多时间。
服务端2026年5月31日 00:26
MQTT QoS 0、1、2 有什么区别?实际项目该怎么选?MQTT QoS 解决的是“消息交付可靠性和成本怎么平衡”的问题。QoS 0 是最多一次,速度最快但可能丢;QoS 1 是至少一次,能保证到达但可能重复;QoS 2 是恰好一次,流程最完整但开销也最大。实际项目里不是 QoS 越高越好,而是要看消息丢失、重复和延迟哪个代价更高。 ## 三种 QoS 的核心区别 QoS 0 只有一条 PUBLISH,没有确认报文。发布者把消息发出去就算完成,网络抖动、客户端断开、Broker 繁忙都可能导致消息丢失。它适合高频遥测,比如温度、湿度、定位点,因为下一条数据很快会覆盖上一条。用 QoS 0 的好处是吞吐高、延迟低、设备耗电少。 QoS 1 会多一个 PUBACK。发布者发送 PUBLISH 后等待确认,如果没收到 PUBACK,就会重发。这样能提高送达概率,但接收方可能收到重复消息,所以业务处理必须幂等。比如告警消息、状态变更、日志上报一般可以用 QoS 1,但要给消息带上 messageId 或事件编号。 QoS 2 使用 PUBLISH、PUBREC、PUBREL、PUBCOMP 四步握手。它的目标是避免重复交付,适合不能重复执行的关键指令。问题是每条消息要更多报文和状态,延迟、内存和磁盘成本都会增加。很多系统口头说“必须恰好一次”,最后真正需要 QoS 2 的消息其实很少。 还要注意一个边界:MQTT 的 QoS 是客户端和 Broker 之间的交付保证,不等于你的业务端到端一定成功。Broker 收到了消息,不代表后端数据库写入成功;订阅者收到了消息,不代表业务处理完成。真正关键的业务还要在 payload 里设计业务流水号、状态机和补偿机制。 QoS 还会影响设备功耗和 Broker 资源。移动网络或卫星链路下,QoS 1 的重传可能让设备反复唤醒无线模块,电池消耗会明显上升。Broker 侧也要保存未确认消息,连接越多、离线越久,堆积风险越大。所以 QoS 选择应该按主题分级,而不是全局一刀切。 ```python import paho.mqtt.client as mqtt client = mqtt.Client(client_id='device-001') client.connect('broker.example.com', 1883, 60) client.publish('device/001/event', '{"id":"evt-1001"}', qos=1) client.loop(timeout=1.0) client.disconnect() ``` 一个比较稳妥的策略是按业务主题设置默认 QoS。状态心跳、实时位置用 QoS 0,告警、配置结果用 QoS 1,涉及扣费、开锁、停机这类强约束命令再评估 QoS 2。即便使用 QoS 2,也不要省掉业务回执,因为设备收到命令和执行成功是两件事。把这两层分清,问题定位会容易很多。 ## 追问 ### QoS 1 为什么会重复? QoS 1 的确认依赖 PUBACK,如果发布者发出了消息但没有收到确认,它只能假设消息没成功,然后重发。问题是接收方可能已经收到并处理了第一条,只是确认包在路上丢了。这个重复不是协议 bug,而是“至少一次”语义的必然代价。实际项目要用业务 ID 做去重,不能假设 QoS 1 永远只到一次。 ### QoS 2 能不能保证业务绝对不重复? 不能把协议层的恰好一次理解成业务层绝对不重复。QoS 2 主要保证 MQTT 报文交付过程不重复,但业务服务处理消息后可能重启、数据库提交可能超时、下游接口也可能重试。边界在于它管的是客户端到 Broker、Broker 到订阅者这段链路。关键业务仍然需要幂等写入和状态校验。 ### 为什么很多物联网数据用 QoS 0? 传感器数据通常是连续上报的,单点丢失不会影响整体趋势。比如温度每 5 秒上报一次,丢一条比排队重传更能接受,因为旧数据很快失去价值。QoS 0 的优势是低延迟、低带宽、低功耗,适合电池设备。取舍是你要接受偶发丢包,并在服务端用时间窗口判断设备是否异常。 ### 控制命令应该用 QoS 2 吗? 不一定。开灯、重启设备、下发配置这类命令更常用 QoS 1 加业务幂等,因为重复执行可以通过 commandId 防住。QoS 2 更适合重复执行会造成严重后果、且设备和 Broker 都能承受额外状态的场景。踩坑点是只提高 QoS,却没有处理命令超时、设备离线和执行回执。命令可靠性通常要靠 QoS、业务 ACK、超时重试一起完成。 ### 订阅 QoS 和发布 QoS 不一样会怎样? 最终投递给订阅者的 QoS 通常取发布 QoS 和订阅 QoS 中较低的那个。比如发布者用 QoS 1,订阅者只订阅 QoS 0,Broker 会按 QoS 0 投递给它。这个规则容易被忽略,导致发布端以为消息可靠,消费端实际没有确认。排查时要同时看发布代码、订阅代码和 Broker 日志,不能只看一端配置。
服务端2026年5月31日 00:26
MQTT 发布订阅是怎么工作的?主题、通配符和 Broker 怎么配合?MQTT 的发布/订阅模式可以理解成“发消息的人不找具体接收者,只把消息交给主题;想要消息的人订阅主题”。发布者只负责把 payload 发到 topic,订阅者只声明自己关心哪些 topic,中间的 Broker 负责匹配和分发。这个设计把生产者和消费者解耦了,所以很适合设备多、上下线频繁、消息一对多的物联网场景。 ## 一条消息是怎么走的? 流程并不复杂。订阅者先连接 Broker,然后发送 SUBSCRIBE,例如订阅 `home/+/temperature`。传感器作为发布者把温度发到 `home/livingroom/temperature`,Broker 发现这个主题匹配订阅规则,就把消息推给订阅者。发布者并不知道谁收到了消息,订阅者也不需要知道消息来自哪台设备,双方只通过 topic 间接关联。 Topic 是 MQTT 路由的核心。它是用斜杠分隔的层级字符串,比如 `tenant/a/device/001/status`。主题区分大小写,`Home` 和 `home` 是两个主题。设计主题时不要只考虑今天的功能,还要考虑权限、统计、扩展和排障,否则后面 ACL 和数据分析都会很难做。 通配符让订阅变得灵活。`+` 匹配单层,例如 `home/+/temperature` 可以匹配客厅和卧室温度。`#` 匹配多层,但只能放在末尾,例如 `home/#` 能收到 home 下所有消息。通配符很方便,也很危险,生产环境要避免业务客户端随便订阅大范围主题。 发布订阅不是消息队列的简单替代品。MQTT 更强调实时推送和连接管理,消息是否离线保存取决于会话、QoS 和 Broker 配置。多个订阅者订阅同一主题时,默认每个订阅者都会收到一份消息;如果想做负载均衡,需要使用共享订阅,例如 MQTT 5 常见的 `$share/group/sensor/#`。 真实项目里还要区分“状态”和“事件”。状态可以用 retained message 保留最后一条,比如设备在线状态;事件则应该进入后端存储,比如告警流水和操作记录。把两者混在一起会出问题:新客户端上线后拿到一条 retained 告警,可能误以为刚刚发生。主题命名和 payload 里最好明确消息类型。 ```bash mosquitto_sub -h localhost -t 'home/+/temperature' mosquitto_pub -h localhost -t 'home/livingroom/temperature' -m '25.6' mosquitto_pub -h localhost -t 'home/kitchen/humidity' -m '60%' ``` 发布订阅还有一个好处是便于旁路扩展。原来只有监控服务订阅设备状态,后来新增告警、数据清洗或调试工具,只要再订阅同一类主题即可,不需要改发布端代码。不过这也带来治理问题:谁订阅了什么、是否还在消费、是否造成重复处理,都需要有可观测性。Broker 侧的订阅列表和消费延迟应纳入日常排查。 ## 追问 ### 发布者和订阅者真的完全不知道彼此吗? 协议层面是解耦的,发布者不需要保存订阅者列表,订阅者也不直接连接发布者。业务层面通常还是会约定 payload 格式、主题命名和设备身份,否则收到消息也不知道怎么处理。取舍在于灵活性和治理成本:解耦让扩展容易,但主题规范一旦缺失,系统会变成没人敢改的消息网。 ### 主题应该怎么设计才不容易后悔? 建议把租户、产品、设备和方向放进主题,例如 `tenant/{tid}/product/{pid}/device/{id}/up`。这样 ACL 可以按路径限制,日志也容易按设备定位。不要把大量业务字段塞进 topic,比如温度值、时间戳应该放 payload,不该放主题。边界是 topic 适合做路由维度,不适合承载所有数据维度。 ### `+` 和 `#` 通配符有什么坑? `+` 只匹配一层,`#` 匹配多层并且只能出现在末尾,这两个规则经常被写错。订阅 `home/#` 会收到 `home` 下几乎所有消息,调试时方便,生产里可能造成流量暴涨。还有一个坑是 ACL 放开了通配符订阅,普通设备就可能读到别人的数据。通配符应该更多给平台服务用,终端设备尽量订阅精确主题。 ### 发布订阅和点对点消息有什么区别? 点对点模式通常知道明确接收者,消息只交给一个目标。MQTT 发布订阅默认是一对多,任何匹配订阅的客户端都能收到消息。它适合状态广播、设备上报、告警通知,不适合需要严格单消费者处理的任务队列。需要负载均衡消费时,可以用共享订阅,但仍要处理重复投递和消费幂等。 ### 为什么订阅后才收到消息?以前的消息去哪了? 普通订阅只接收订阅建立之后的新消息,之前发布的消息不会自动补发。想让新客户端上线就拿到最近状态,可以用 Retained Message;想让离线客户端恢复后收到消息,要使用持久会话和合适的 QoS。踩坑点是把 retained 当历史消息,它只保留每个主题最后一条。真正的历史查询应该从数据库查,而不是指望 Broker 保存全部消息。
服务端2026年5月31日 00:26
MQTT Broker 负责什么?Mosquitto、EMQX 和 HiveMQ 怎么选?MQTT Broker 是 MQTT 系统里的中枢,不只是“转发消息”的服务器。它要维护客户端连接、处理认证授权、保存订阅关系、按主题路由消息,还要根据 QoS 管理确认、重传和离线消息。选 Broker 时不能只看宣传里的百万连接,更要看你的消息量、持久化要求、集群能力、运维团队是否能长期维护。 ## Broker 到底做哪些事? 第一件事是连接管理。客户端通过 CONNECT 建立长连接,Broker 要校验 Client ID、账号、证书和 Keep Alive。连接建立后,它还要发现客户端是否掉线,并在异常断开时发布遗嘱消息。连接数一多,文件句柄、内存、心跳间隔都会变成真实的容量问题。 第二件事是主题路由。发布者把消息发到某个 topic,Broker 根据订阅关系找到匹配的订阅者。这里不只是字符串匹配那么简单,还涉及 `+`、`#` 通配符、共享订阅、保留消息和 ACL。主题层级设计得好,Broker 的规则就清晰;主题乱了,后面无论换什么产品都很难救。 第三件事是可靠性和存储。QoS 1 要保存未确认消息,QoS 2 要维护更完整的握手状态,持久会话还要保存离线消息。很多人压测只测 QoS 0 在线消息,结果上线后一开持久会话,磁盘和内存马上顶不住。Broker 不是数据库,离线消息要设置过期和队列上限。 常见实现里,Mosquitto 轻量、简单,适合边缘网关、实验室和小型项目。EMQX 功能完整,规则引擎、集群、管理界面都比较成熟,适合物联网平台。HiveMQ 企业能力强,商业支持好,适合预算充足、稳定性要求高的团队。RabbitMQ 的 MQTT 插件适合已有 RabbitMQ 体系的公司,但它不是专用 MQTT Broker,协议能力和超大连接场景要谨慎评估。 选型时还要把运维能力算进去。Broker 需要监控连接数、订阅数、消息速率、队列堆积、认证失败和磁盘水位,不是启动一个容器就结束。规则引擎、桥接、Webhook 看起来方便,但每增加一条链路,就多一个延迟和失败点。小团队宁可先把核心链路跑稳,也不要一开始把所有高级功能都打开。 ```bash docker run -d --name emqx -p 1883:1883 -p 18083:18083 emqx/emqx:latest mosquitto_sub -h localhost -t 'demo/#' mosquitto_pub -h localhost -t demo/test -m 'hello mqtt' ``` 如果只是学习 MQTT,可以先用 Mosquitto,因为它足够透明,日志和配置都容易理解。如果目标是业务平台,最好尽早验证 EMQX 或 HiveMQ 这类产品的认证、规则转发、监控和集群能力。不要等设备已经铺出去以后再换 Broker,客户端协议版本、证书、主题和重连策略都会牵一发动全身。Broker 选型越靠前做,迁移成本越低。 ## 追问 ### Mosquitto 和 EMQX 最大区别是什么? Mosquitto 的优势是轻、小、部署快,几分钟就能跑起来。EMQX 更像平台型 Broker,集群、规则引擎、认证插件和监控能力更完整。取舍很直接:边缘侧或小项目用 Mosquitto 很舒服,中心平台和多租户接入更适合 EMQX。不要因为“未来可能百万连接”就一开始上复杂集群,运维复杂度也是真成本。 ### Broker 能不能当消息队列长期存数据? 不建议。MQTT Broker 可以保存离线消息、保留消息和 QoS 状态,但它的目标是实时分发,不是长期存储和复杂查询。历史数据应该落到时序数据库、对象存储或业务数据库里。踩坑点是离线设备太多时消息堆积,如果没有过期时间和队列上限,Broker 会被自己的可靠性功能拖垮。 ### 集群部署时最难的地方是什么? 难点不是把多个节点启动起来,而是会话、订阅关系和消息路由如何在节点间同步。共享订阅、持久会话、QoS 1/2 都会让集群状态变重。边界是网络分区:节点之间一旦抖动,客户端可能重连到不同节点,重复投递和短暂不可达都要在业务侧兜底。实际项目要压测故障切换,而不是只压测正常吞吐。 ### 选 Broker 时要看哪些指标? 至少看四个指标:并发连接数、每秒消息数、QoS 级别、消息大小。还要看认证方式、ACL 复杂度、持久化策略和监控告警能力。只报“百万连接”没有意义,因为一百万空闲连接和十万高频上报连接完全不是一个负载。选型时最好用自己的主题结构和真实 payload 做压测。 ### RabbitMQ 插件适合 MQTT 场景吗? 如果公司已经大量使用 RabbitMQ,只需要少量设备接入 MQTT,它可以降低系统数量。问题是 RabbitMQ 的核心模型不是为海量 MQTT 长连接设计的,通配符、会话、共享订阅等能力也要逐项确认。高并发设备接入、复杂 ACL 和物联网规则处理更适合专用 Broker。这里的取舍是复用现有基础设施,还是为 MQTT 场景单独建设更合适的接入层。
服务端2026年5月31日 00:26
MQTT 通信如何保证安全?TLS、认证和 ACL 怎么搭配?MQTT 安全不能只靠一个用户名密码。比较稳的做法是分三层:先用 TLS 保护链路,再用认证确认“谁连上来”,最后用 ACL 限制“它能发布和订阅什么主题”。如果消息本身很敏感,还要在应用层做加密或签名,因为 TLS 只保护传输过程,Broker 收到消息后仍然能看到明文。 ## MQTT 安全主要防什么? MQTT 常见风险有四类:明文传输被抓包、弱口令被撞库、客户端越权订阅主题、伪造设备发送控制指令。1883 端口默认不加密,在测试环境很方便,但放到公网基本等于把账号和消息内容暴露出去。生产环境通常使用 8883 端口跑 MQTT over TLS,客户端校验 Broker 证书,避免连到假 Broker。 认证解决的是身份问题。最常见的是用户名和密码,也可以使用 JWT、OAuth2 Token 或客户端证书。用户名密码实现简单,但必须配合 TLS,否则 CONNECT 报文里的凭据可能被截获。证书认证更适合设备数量可控、生命周期长的场景,缺点是证书签发、吊销和更新都要有流程。 授权靠 ACL 落地。不要给设备订阅 `#` 或发布任意主题的权限,主题最好带上租户、产品、设备 ID,例如 `tenant/a/device/001/up`。服务端只允许设备发布自己的上行主题,只允许订阅自己的下行主题。这个规则看起来啰嗦,但能防止一个设备越权读取另一台设备的数据。 还有一个经常被忽略的点是 Client ID。很多设备 SDK 示例会写死 `client1`,测试时没问题,上线后多个设备互相顶号,安全排查会非常混乱。Client ID 最好和设备身份绑定,并在 Broker 侧限制同一身份的连接策略。日志里也要保留客户端 IP、用户名、Client ID、订阅主题和拒绝原因,否则出事后只能猜。 ```conf listener 8883 certfile /etc/mosquitto/certs/server.crt keyfile /etc/mosquitto/certs/server.key cafile /etc/mosquitto/certs/ca.crt allow_anonymous false password_file /etc/mosquitto/passwd acl_file /etc/mosquitto/acl ``` ```bash mosquitto_passwd -c /etc/mosquitto/passwd device001 mosquitto_pub -h broker.example.com -p 8883 --cafile ca.crt -u device001 -P 'secret' -t tenant/a/device/001/up -m '{"temp":25}' ``` 还有一个实用做法是把安全配置分环境管理。开发环境可以使用本地 Broker 和临时账号,但预发、生产必须强制 TLS、禁止匿名、收紧 ACL,并把配置变更纳入审计。不要把 Broker 管理后台直接暴露到公网,管理端口应放在内网或 VPN 后面。安全不是一次性配置,证书过期、员工离职、设备报废都会让原本安全的系统慢慢变脆。 ## 追问 ### 只开 TLS 就够了吗? 不够,TLS 只能说明链路是加密的,并不能自动说明客户端有业务权限。一个合法设备如果拿到了通配符订阅权限,仍然可以看到不该看的主题。实际项目里 TLS、认证、ACL 要一起上,少一层都会留下明显缺口。取舍在于成本:内网测试可以先用用户名密码,公网和多租户环境至少要 TLS 加 ACL。 ### 用户名密码和客户端证书怎么选? 用户名密码适合设备多、接入快、需要后台批量生成凭据的系统,运维成本低。客户端证书更适合网关、工业设备、金融终端这类安全要求高且设备数量可控的场景。证书的坑在于过期和吊销,很多事故不是加密算法不安全,而是证书到期后设备大面积掉线。选择时要看你有没有完整的证书生命周期管理能力。 ### ACL 规则最容易踩什么坑? 最常见的坑是主题设计太随意,后面 ACL 无法精确表达权限。比如所有设备都往 `data/upload` 发消息,Broker 很难区分来源,只能把鉴权压力挪到应用层。更好的做法是从第一天就把租户、产品、设备 ID 放进主题路径。边界也要注意:`+` 只能匹配一级,`#` 必须放在末尾,误用通配符会造成越权订阅。 ### 消息还需要端到端加密吗? 如果 Broker 本身可信,TLS 通常已经够用,因为 Broker 需要读取主题并转发消息。如果消息经过第三方云 Broker,或者内容是医疗、金融、门锁指令,就要考虑应用层加密和签名。加密会带来密钥分发、调试困难和规则引擎无法解析 payload 的代价。实际取舍是:遥测数据通常只签名或走 TLS,关键控制命令更适合加签名、防重放和短有效期。 ### 如何发现 MQTT 安全配置有问题? 先检查 1883 是否暴露公网,再检查是否允许匿名连接和弱密码。然后用测试账号尝试订阅 `#`、发布到别的设备主题,看 ACL 是否真的生效。日志里要关注频繁 CONNECT 失败、异常 Client ID、短时间大量订阅等行为。很多问题不是 Broker 没有安全能力,而是默认配置太宽松,上线前没有做一次越权测试。
服务端2026年5月31日 00:26
MQTT 5.0 比 3.1.1 多了什么?哪些特性值得升级?MQTT 5.0 不是把 3.1.1 推倒重来,而是在原有发布/订阅模型上补齐了工程化能力。它解决的重点不是“能不能发消息”,而是消息过期怎么表达、错误原因怎么定位、请求响应怎么关联、客户端能力怎么协商,以及高并发系统里如何做流量控制。 ## 最值得关注的新特性 第一类是属性 Properties。MQTT 5.0 给很多控制报文增加了属性字段,可以携带内容类型、响应主题、关联数据、消息过期时间、用户属性等元信息。以前这些信息通常要塞进 payload,客户端和服务端各自约定格式;现在协议层有标准位置,跨团队和跨语言接入会少很多口头约定。 第二类是会话和消息过期。MQTT 3.1.1 主要靠 Clean Session 表达是否保留会话,语义偏粗。MQTT 5.0 用 Session Expiry Interval 指定会话多久过期,用 Message Expiry Interval 指定消息多久后不再投递。对离线设备很有用:告警消息可以保留,过期的实时温度就没必要等设备上线后再补发。 第三类是请求/响应模式。通过 Response Topic 和 Correlation Data,客户端可以发布请求,再从指定响应主题拿到结果。它不会把 MQTT 变成 HTTP,但能让设备配置读取、远程诊断、指令确认这类场景更规范。边界是它仍然是异步消息模型,超时、重试和权限仍要应用层设计。 第四类是流量控制和诊断能力。Receive Maximum 可以限制未确认 QoS 1/2 消息数量,Maximum Packet Size 可以拒绝过大的报文,Reason Code 能告诉你是未授权、主题名非法还是服务器繁忙。3.1.1 里很多失败只像“连接断了”,5.0 至少能让排查方向更明确。 第五类是共享订阅、主题别名和订阅标识符。共享订阅让多个消费者分摊同一类消息,适合后端处理集群;主题别名用数字代替长主题,适合高频上报;订阅标识符能帮助客户端判断消息命中了哪个订阅规则。 ## 升级时怎么取舍? 如果系统只是低频传感器上报,3.1.1 已经稳定运行,升级收益可能不大。若你正在做多租户 IoT 平台、设备远程控制、复杂权限、批量消费者扩容或问题排查成本很高,5.0 的收益会明显。真正的成本在兼容性:Broker、SDK、网关、监控工具和设备固件都要确认支持程度。 ```python # paho-mqtt v2 示例:发布带 MQTT 5 属性的消息 from paho.mqtt.client import Client from paho.mqtt.packettypes import PacketTypes from paho.mqtt.properties import Properties client = Client(protocol=5) client.connect('broker.example.com', 1883) props = Properties(PacketTypes.PUBLISH) props.MessageExpiryInterval = 30 props.ContentType = 'application/json' client.publish('device/1/status', '{"online":true}', qos=1, properties=props) ``` 迁移前最好列一张能力清单:Broker 是否支持 5.0,客户端 SDK 是否支持属性,监控系统能否展示原因码,网关是否会丢弃用户属性。只升级协议版本但不改日志和告警,收益会被打折。更稳妥的方式是先在少量设备上启用消息过期、原因码和最大报文限制,确认没有兼容问题后再扩大范围。 ## 追问 ### MQTT 5.0 能完全兼容 3.1.1 吗? 不能理解成所有特性自动兼容。5.0 Broker 通常可以接受 3.1.1 客户端,5.0 客户端也可以按 3.1.1 协议连接旧 Broker,但 5.0 属性、原因码、会话过期等能力不会凭空生效。迁移时要确认每个 SDK 实际使用的 protocol version。踩坑点是 Broker 升级了,但边缘设备 SDK 仍按 3.1.1 连接,结果新特性根本没用上。 ### Session Expiry 和 Clean Session 有什么区别? Clean Session 更像开关,要么清理,要么保留。Session Expiry 是时间维度,可以表达断开后保留 10 分钟、1 天或一直保留。这个能力适合移动网络不稳定的设备,短暂掉线不丢订阅和未确认消息。取舍是会话保留越久,Broker 存储和清理压力越大,不能无脑设置永不过期。 ### Message Expiry 解决了什么实际问题? 它解决的是“过期消息还要不要送”的问题。实时温度、位置、在线状态这类数据,过了几十秒再送可能没有意义,甚至会污染业务判断。设置过期时间后,Broker 可以在消息失效后停止分发。边界是关键指令不要随便设置太短,否则设备网络抖动时可能错过真正需要执行的命令。 ### 共享订阅是不是等同于 Kafka 消费组? 它们目的相似,都是让多个消费者分摊消息,但语义和生态不一样。MQTT 共享订阅更轻量,适合 Broker 直接把消息分给后端实例;Kafka 消费组更强调日志存储、offset 和回放。若你需要长期保留、重放和批处理,Kafka 更合适。若只是把设备上报实时分摊给多个处理服务,MQTT 共享订阅就够用。 ### 为什么 Reason Code 对运维很重要? 没有明确原因码时,连接失败经常只能猜:密码错、ACL 拒绝、协议版本不对、报文太大都可能表现为断开。MQTT 5.0 的 Reason Code 能把失败原因直接带回来,日志和监控可以按原因聚合。踩坑点是客户端要把原因码打印出来,不能只记录“connect failed”。否则协议给了诊断信息,应用层却把它丢了。
服务端2026年5月31日 00:26
MQTT 有哪些控制报文?连接、发布和订阅流程怎么串起来?MQTT 控制报文不是一张要死背的清单,而是一套围绕连接、发布、订阅、心跳和断开的状态机。理解它们的最好方式,是先把流程跑通:客户端用 CONNECT 建立连接,Broker 用 CONNACK 回应;发布消息用 PUBLISH,不同 QoS 会带出 PUBACK、PUBREC、PUBREL、PUBCOMP;订阅主题用 SUBSCRIBE/SUBACK,取消订阅用 UNSUBSCRIBE/UNSUBACK;空闲时靠 PINGREQ/PINGRESP 保活,正常退出用 DISCONNECT。 ## 报文类型怎么分组? 连接类只有 CONNECT 和 CONNACK。CONNECT 里会带 Client ID、用户名密码、Keep Alive、Clean Session 或 MQTT 5.0 的会话过期设置,还可以带遗嘱消息。CONNACK 告诉客户端是否连接成功,失败时会返回原因码或返回码。 发布类以 PUBLISH 为中心。QoS 0 只发 PUBLISH,不等确认;QoS 1 是 PUBLISH 加 PUBACK,保证至少一次;QoS 2 是 PUBLISH、PUBREC、PUBREL、PUBCOMP 四步,目标是恰好一次。这里最容易混淆的是 QoS 2 的“恰好一次”只针对 MQTT 投递流程,不代表业务处理绝对只发生一次。 订阅类包括 SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK。SUBSCRIBE 可以一次带多个 Topic Filter,例如 `home/+/temperature` 或 `factory/#`。SUBACK 会逐个返回订阅结果,别只看报文到了没到,还要检查每个主题是否被授权。 心跳和断开类包括 PINGREQ、PINGRESP、DISCONNECT。Keep Alive 到期前客户端需要发心跳;如果 Broker 在 1.5 倍 Keep Alive 时间内没收到任何控制报文,通常会认为连接失效。正常 DISCONNECT 不会触发遗嘱消息,异常断线才会触发,这个细节在告警系统里很重要。 ## 固定头部怎么看? 所有 MQTT 控制报文都有固定头部,第一字节高 4 位是报文类型,低 4 位是标志位,后面是 Remaining Length。PUBLISH 的 flags 会携带 DUP、QoS、Retain,PUBREL、SUBSCRIBE、UNSUBSCRIBE 的固定标志位也有固定要求,写客户端或排查抓包时不能乱填。 ```text Byte 1: Message Type(4 bits) + Flags(4 bits) Byte 2+: Remaining Length(variable byte integer) Next: Variable Header + Payload ``` ```bash # 用 mosquitto 快速观察订阅和发布流程 mosquitto_sub -d -h test.mosquitto.org -t 'demo/packet' mosquitto_pub -d -h test.mosquitto.org -t 'demo/packet' -q 1 -m 'hello' ``` ## 一条消息会经过哪些报文? 以设备上报一条 QoS 1 温度消息为例,客户端先 CONNECT,Broker 返回 CONNACK 后才算连接建立。订阅端发送 SUBSCRIBE,Broker 用 SUBACK 确认订阅结果;发布端发送 PUBLISH,Broker 收到后返回 PUBACK,同时按主题匹配把消息转发给订阅端。连接空闲期间客户端继续用 PINGREQ 保活,Broker 回 PINGRESP;设备正常下线时发送 DISCONNECT,Broker 就不会发布遗嘱消息。 如果换成 QoS 2,发布链路会多出 PUBREC、PUBREL、PUBCOMP。这个流程看起来啰嗦,但它把“收到消息”和“释放消息”拆成两个阶段,避免网络抖动时双方状态不一致。代价也很明显:Broker 和客户端都要保存更多中间状态,吞吐下降,排查时也要关注 Packet Identifier 是否被复用或卡住。 ## MQTT 5.0 里的变化 MQTT 3.1.1 常说 14 种控制报文,MQTT 5.0 增加了 AUTH,并且给很多报文加了属性和原因码。AUTH 用于增强认证,适合需要多轮认证或重新认证的场景。原因码让 CONNACK、PUBACK、SUBACK、DISCONNECT 等报文能表达更细的失败原因,这对线上排障很有价值。 ## 追问 ### QoS 0、1、2 分别会触发哪些确认报文? QoS 0 没有确认报文,消息发出后协议层就不再追踪。QoS 1 需要 PUBACK,所以断线或超时后可能重发,接收方要能处理重复消息。QoS 2 要经过 PUBREC、PUBREL、PUBCOMP,流程最完整但延迟和状态存储也最多。取舍很直接:越可靠,报文越多,吞吐和实现复杂度越受影响。 ### CONNECT 里最容易配错哪些字段? Client ID、Clean Session、Keep Alive 和遗嘱消息最容易出问题。多个设备复用同一个 Client ID 会互相踢下线,看起来像网络不稳定。Keep Alive 设置太短会制造无意义心跳,太长又会拖慢离线检测。遗嘱消息要配合异常断线理解,主动 DISCONNECT 不会触发它。 ### SUBACK 收到了就代表订阅成功吗? 不一定。SUBACK 只是 Broker 回应了订阅请求,真正要看每个返回码或原因码。某些主题可能因为 ACL 被拒绝,客户端如果只判断“收到 SUBACK”就会误以为订阅成功。项目里建议把订阅结果写入日志,并在关键主题失败时直接告警。边界是:协议完成和业务可用不是同一件事。 ### Remaining Length 为什么是可变长度编码? MQTT 面向小设备和小消息,固定用 4 字节表示长度会浪费空间。可变长度编码让小报文只用 1 个字节表示长度,大报文再逐步扩展。踩坑点是它最多占 4 字节,而且每个字节只有 7 位表示数值,最高位表示是否还有后续字节。自己写解析器时如果没处理非法超长编码,可能被恶意报文拖住。 ### 抓包时如何快速判断 MQTT 流程卡在哪里? 先看 CONNECT 后有没有 CONNACK,没有就查网络、TLS、认证和协议版本。发布卡住时看 QoS:QoS 1 缺 PUBACK,QoS 2 缺哪一步就查对应的会话状态。订阅收不到消息时别只盯 PUBLISH,要检查 SUBACK 返回码、Topic Filter 是否匹配、Retain 和 ACL。排查边界是先确认协议层报文完整,再看业务载荷是否符合预期。
服务端2026年5月31日 00:26
MQTT 和 HTTP 有什么区别?物联网场景该怎么选?MQTT 和 HTTP 都跑在应用层,很多时候也都基于 TCP,但它们解决的问题不一样。HTTP 更像一次明确的业务请求:客户端问,服务端答,适合查数据、提交表单、上传文件和调用 REST API。MQTT 更像一个消息中转站:设备把消息发到主题,订阅者按主题接收,发布者不需要知道谁在听。 ## 核心区别是什么? 最明显的区别是通信模型。HTTP 是请求/响应,天然以客户端发起为中心;MQTT 是发布/订阅,Broker 负责路由消息,可以把一条温度数据同时分发给监控面板、告警服务和数据入库服务。 第二个区别是连接方式。HTTP/1.1 可以 Keep-Alive,HTTP/2 也能复用连接,但多数业务仍围绕“请求完成即返回”设计。MQTT 通常保持长连接,通过 Keep Alive 和 PINGREQ/PINGRESP 确认连接还活着,这对设备在线状态和服务端主动下发指令很关键。 第三个区别是报文开销。MQTT 固定头部最小 2 字节,主题和载荷之外的额外开销很小;HTTP 请求头常常包含 Cookie、User-Agent、鉴权头等信息,几百字节很常见。对 4G 模组、NB-IoT、卫星链路或电池供电设备来说,这些差距会直接变成流量费和续航差距。 ## 场景怎么选? 设备遥测、实时状态、告警推送、远程控制更适合 MQTT。例如一万个传感器每 5 秒上报一次温度,用 HTTP 轮询会制造大量连接和请求头开销,用 MQTT 长连接发布到 `factory/line1/temperature` 更自然。 Web 页面、后台管理、文件上传、复杂查询更适合 HTTP。比如查询某台设备过去 7 天的历史曲线,用 HTTP API 带分页、筛选条件和缓存策略更好维护。实际项目里常见做法是混用:MQTT 负责实时上报和控制,HTTP 负责配置、报表、账号体系和历史查询。 ## 快速验证示例 ```bash # 订阅温度主题 mosquitto_sub -h test.mosquitto.org -t 'demo/device1/temp' # 另一个终端发布消息 mosquitto_pub -h test.mosquitto.org -t 'demo/device1/temp' -m '25.6' ``` ```bash # 同样的数据用 HTTP 提交 curl -X POST https://api.example.com/devices/device1/temperature \ -H 'Content-Type: application/json' \ -d '{"value":25.6}' ``` ## 实战里的判断顺序 选型时不要先问哪个协议更先进,而要先看数据流向、频率和失败后果。若设备每隔几秒上报一次状态,并且平台需要随时下发控制命令,MQTT 的长连接会让链路更简单。若用户只是偶尔打开页面查一次报表,HTTP 的一次请求一次响应更符合直觉,日志、鉴权、缓存和排障工具也更成熟。 还要看团队的运维能力。MQTT 引入 Broker 后,要管理主题命名、ACL、离线消息、会话、重连风暴和保留消息;HTTP 则更多依赖 API 网关、负载均衡和服务端限流。很多项目不是败在协议本身,而是没有提前定义边界:哪些消息必须实时,哪些数据必须可追溯,哪些请求失败后允许重试。 ## 追问 ### MQTT 一定比 HTTP 更省资源吗? 不一定,要看连接生命周期和消息频率。高频、小包、需要服务端下发的场景,MQTT 的长连接和小头部优势明显。低频操作反而可能是 HTTP 更省心,因为不用维护在线状态、重连和主题权限。踩坑最多的是把所有业务都塞进 MQTT,最后发现查询、分页、审计和重放都比 HTTP 难做。 ### 如果 HTTP 有长连接和 HTTP/2,还需要 MQTT 吗? HTTP/2 解决的是多路复用和传输效率,不等于提供发布/订阅、QoS、遗嘱消息和主题路由。你可以用 HTTP/2 做实时接口,但服务端主动把消息分发给多个订阅方时,应用层要自己补一套消息系统。边界在于业务是否以“资源访问”为核心,还是以“消息流转”为核心。前者继续用 HTTP,后者更像 MQTT 的主场。 ### MQTT 的 QoS 能替代业务幂等吗? 不能。QoS 1 只能保证至少送达,重复消息很正常;QoS 2 能减少重复投递,但成本更高,也不能替你处理业务端重复扣费、重复开锁这类问题。实际项目里关键指令仍要带 messageId,并在服务端做去重。取舍是:协议层保证传输语义,业务层保证业务结果。 ### 为什么很多系统同时使用 MQTT 和 HTTP? 因为两者擅长的部分互补。设备上线、心跳、实时数据和控制命令走 MQTT,用户登录、设备列表、固件下载、历史报表走 HTTP,会比单押一种协议更稳。踩坑点是鉴权体系要统一,否则 MQTT 的 ACL 和 HTTP 的用户权限容易出现不一致。通常会用同一套账号或 Token 签发逻辑,再分别落到 Broker 和 API 网关。 ### MQTT 适合传文件或大报文吗? 一般不推荐。MQTT 可以传二进制载荷,但 Broker、客户端内存、最大报文限制和重传成本都会放大风险。固件包、图片、日志压缩包更适合 HTTP、对象存储或 CDN,MQTT 只通知下载地址和版本号。这个边界很重要,否则一次大文件重传就可能拖垮低端设备或 Broker 队列。
服务端5月30日 23:35
MQTT Last Will 为什么能发现设备异常离线?MQTT Last Will(遗嘱消息,LWT)是在客户端连接 Broker 时预先登记的一条消息。当客户端异常断开,比如断电、进程崩溃、网络中断或 Keep Alive 超时,Broker 会把这条消息发布到指定 Topic。它最常见的用途是设备在线状态:设备正常工作时发布 `online`,异常离线时由 Broker 代发 `offline`。它解决的是“设备突然没了,其他系统怎么知道”的问题,但不能告诉你离线原因,也不能替代完整的监控系统。在物联网项目里,LWT 更像一个兜底信号:它能尽快告诉你连接不可靠了,但后续仍要靠日志、最后上报时间和设备自检来定位原因。 ## 它什么时候会触发? Will 消息在 CONNECT 阶段设置,包含 Will Topic、Payload、QoS 和 Retain。客户端正常发送 DISCONNECT 时不会触发,因为 Broker 认为这是有计划的离线。真正触发的是非正常断开:TCP 连接断掉、心跳超时、客户端进程被杀、设备断电等。这里有个边界:如果连接根本没建立成功,Broker 没收到完整 CONNECT,自然也没有遗嘱可发。另一个边界是触发时间不一定等于真实掉线时间,Broker 往往要等 Keep Alive 超时后才确认连接失效。 可以用 mosquitto 做一个最小实验: ```bash mosquitto_sub -h localhost -t 'device/123/status' -v mosquitto_pub -h localhost -i dev123 --will-topic device/123/status --will-payload '{"status":"offline"}' --will-retain -l ``` 第二个命令保持标准输入不退出,直接强杀进程或断网,订阅端会看到 `offline`。如果你按正常方式退出并发送 DISCONNECT,就不会触发遗嘱。测试时不要只关闭终端标签页,有些客户端会优雅断开,最好用 `kill -9` 或断开网络来模拟异常。真实设备还要测试休眠、基站切换、路由器重启这些场景,因为它们比本地进程崩溃更接近生产问题。如果 Broker 是集群,还要确认会话迁移和节点故障时 Will 的行为,避免某个节点重启就把一批设备误判为离线。 ## 追问 ### Last Will 和 retained message 是什么关系? Last Will 决定“异常断开时发什么”,retained message 决定“这条消息要不要被 Broker 保存给后来订阅者”。两者可以单独使用,也经常组合使用。设备上线时主动向 `device/123/status` 发布 retained 的 `online`,连接时设置 retained 的 Will 为 `offline`,这样新订阅者随时都能看到最新在线状态。取舍是旧状态会被保存,所以设备退役、迁移 Topic 或更换设备 ID 时必须清理 retained 消息。 ### Keep Alive 应该设置多长? Keep Alive 越短,Broker 越快发现设备异常,遗嘱消息越及时;但心跳更频繁,弱网和电池设备会付出额外成本。工业网关或后台服务可以设 30-60 秒,低功耗设备可能设几分钟。不要为了“秒级离线”把 Keep Alive 调得极小,网络抖动会制造误报。更稳的做法是结合业务容忍度、网络质量和告警成本来定,并在应用层记录最后一次业务上报时间。比如状态页可以 1 分钟内显示疑似离线,告警系统再等两个心跳周期确认,减少无意义的电话和短信轰炸。 ### Will QoS 和 Retain 怎么选? 状态看板通常选择 QoS 1 加 retain,因为离线状态值得至少送达一次,并且后来打开页面的人也需要看到。普通日志或临时告警可以不 retain,避免新订阅者反复看到旧告警。QoS 2 很少必要,它会增加握手成本,且多数离线状态处理本身就应该幂等。真正关键的是消费端按设备 ID 去重,别因为重复投递把同一台设备告警多次。 ### 代码里怎么设置遗嘱消息? Python paho-mqtt 必须在 `connect()` 之前调用 `will_set()`,这是很多人第一次用时会踩的坑。连接成功后再设置不会影响当前连接,只能等下次连接才生效。上线状态建议在连接成功后立刻发布,最好也 retain,避免还没连上就误以为设备在线。示例: ```python client = mqtt.Client(client_id='dev123') client.will_set('device/123/status', '{"status":"offline"}', qos=1, retain=True) client.connect('broker.example.com', 1883, keepalive=60) client.publish('device/123/status', '{"status":"online"}', qos=1, retain=True) ``` ### 实际项目里最容易误判什么? 第一是把网络抖动当成设备故障,尤其移动网络和 Wi-Fi 漫游场景很常见。第二是只依赖 LWT 判断离线,却没有应用层最后上报时间,结果 Broker 故障、集群切换或会话迁移时不好排查。第三是多客户端共用同一个 Client ID,后上线的连接会踢掉前一个连接,可能触发前一个连接的遗嘱。生产环境里要保证 Client ID 唯一,并把 `offline` 消息设计成幂等事件;正常维护、主动关机这类离线则应由客户端主动发布状态再 DISCONNECT,异常离线才交给 Will。如果业务需要区分原因,可以把 payload 设计成 `status`、`reason`、`ts` 三个字段,但不要指望 Will 自动知道真实原因。
服务端5月30日 23:35
MQTT Retained Message 为什么新订阅者会收到旧消息?MQTT retained message 是 Broker 为某个 Topic 保存的“最后一条状态消息”。发布端设置 `retain=true` 后,Broker 会把这条消息存下来;后来才订阅该 Topic 的客户端,不用等设备再次上报,也会立刻收到这条保留消息。它适合表达“当前状态”,不适合表达“历史事件”。温度、开关状态、设备配置、系统版本这类值很适合;告警流水、订单事件、聊天消息就不适合,否则新订阅者会误以为旧事件刚刚发生。 ## 它到底解决什么问题? 没有 retained message 时,新客户端订阅 `sensor/123/temperature` 后只能等待下一次发布。如果传感器 10 分钟才上报一次,页面刚打开时就会空白 10 分钟。设置保留消息后,Broker 会在订阅成功时先补发最新值,例如 `{"value":25.5,"unit":"C","ts":1710000000}`。注意每个 Topic 只保留一条,新的 retained 消息会覆盖旧的,不能把它当数据库用。它的价值在于让订阅方快速拿到“现在是什么”,而不是让订阅方知道“刚才发生过什么”。在设备管理后台、Home Assistant、车联网网关里,这个差异很关键:状态页需要立刻显示当前值,审计页才需要完整历史。 可以用 mosquitto 快速验证: ```bash mosquitto_pub -h localhost -t sensor/123/temperature -m '{"value":25.5}' -r mosquitto_sub -h localhost -t sensor/123/temperature -v mosquitto_pub -h localhost -t sensor/123/temperature -n -r ``` 第三条命令发布空 Payload 并带 retain,用来清除该 Topic 的保留消息。很多踩坑都出在这里:只发布空消息但忘了 `-r`,Broker 不会删除 retained 记录。另一个常见问题是以为订阅通配符时 Broker 只返回一条,实际上订阅 `sensor/+/temperature` 时,匹配到的每个具体 Topic 如果有 retained 记录,都可能各发一条。第一次接入大量设备时,这会让订阅端瞬间收到一批初始化消息,所以消费端要能区分初始化流量和实时流量。服务端也要限制 retained 消息大小,别把整份配置文件、证书或图片塞进去;大对象更适合放对象存储,MQTT 里只放版本号和下载地址。 ## 追问 ### Retained message 和普通消息有什么区别? 普通消息只发给当时在线且已订阅的客户端,错过就错过了;retained message 会被 Broker 留一份,后来的订阅者也能拿到。这个机制的取舍是牺牲一部分 Broker 存储,换来状态初始化更快。边界也很明确:它只表示“最后状态”,不保证覆盖状态变化过程。如果业务需要追溯每次变化,应该写入时序库、日志系统或消息队列,而不是堆 retained Topic。还有一种折中做法是 retained 只保存最新摘要,历史明细由后端 API 查询,这样页面既能秒开,又不会让 Broker 背负数据库职责。 ### QoS 会怎样影响保留消息? 保留消息发布时可以带 QoS 0、1、2,Broker 保存的也是这条消息及其 QoS。订阅者实际收到的 QoS 通常是 `min(发布 QoS, 订阅 QoS)`,所以发布 QoS 1 不代表每个订阅者都按 QoS 1 收到。一般状态展示用 QoS 0 就够,关键配置可以用 QoS 1。QoS 2 成本更高,会增加 Broker 和客户端的握手负担,只有在重复处理代价很大、链路又必须严格确认时才值得使用。 ### 什么场景不应该使用 retained message? 不要把告警、支付回调、门锁开门记录这类事件做成 retained message。新客户端订阅后收到旧告警,很容易触发重复通知或误判现场还在故障。也不要在高频遥测上滥用,例如每秒上报几千个不同 Topic,Broker 的 retained 索引和持久化会被放大。更稳妥的做法是只保留少量状态 Topic,把高频数据写到专门的存储系统,再由应用层按时间范围查询。 ### 代码里怎么发布和识别 retained message? Python 的 paho-mqtt 里,发布时传 `retain=True`,订阅端可以通过 `msg.retain` 判断这是不是 Broker 补发的旧状态。这个标志很有用,页面初始化时可以直接渲染 retained 状态,但不要把它当作“刚刚发生”的事件。上线初始化逻辑和实时事件逻辑最好分开,否则统计、告警和审计都容易被旧消息污染。示例: ```python client.publish('sensor/123/temperature', '{"value":25.5}', qos=1, retain=True) def on_message(client, userdata, msg): if msg.retain: print('initial state:', msg.payload.decode()) else: print('live update:', msg.payload.decode()) ``` ### 使用 retained message 最容易踩什么坑? 第一是忘记清理,设备下线或被删除后,旧状态仍然留在 Broker,新页面看到的还是“在线”。第二是 Topic 设计太粗,比如所有设备共用 `device/status`,结果后发布的设备覆盖前一个设备,排查时看起来像设备互相串线。第三是 retained 和 Last Will 混用时没有规划好在线状态:上线消息应该主动发布 `online` 并 retain,异常掉线再由遗嘱发布 `offline` 并 retain。这样新订阅者看到的才是当前状态,而不是一段过期故事;如果设备退役,还要发布空 retained 消息把状态清掉。