☰
Spring Boot 对接 MQTT 生产级接入指南:从连接管理到消息解耦
2026/10/1 2:16:41 网站建设 项目流程

做设备数据采集时,我第一次被要求在Spring Boot项目里接入MQTT,搜了一圈资料后发现一个尴尬的现象:网上的demo都能跑,但没有一篇能直接搬到生产环境。它们要么把MqttClient随手new在Controller里,要么直接在回调里写业务逻辑,等设备量一起来,连接断了没人重连,消息堆了没人消费。这篇文章就围绕Spring Boot对接MQTT这件事,分享一套我实际打磨过的接入方案,从选型、依赖、代码结构到生产环境的坑,一次讲清。

1. 对接前先想清楚:MQTT解决的是设备通信问题,不是消息堆积问题

1.1 为什么是MQTT而不是Kafka或RabbitMQ

先花点时间厘清一个很容易踩的坑:MQTT常被和消息队列放在一起比较,但它的核心定位完全不同。MQTT全称是Message Queuing Telemetry Transport,是一种为低带宽、高延迟、网络不稳定的物联网场景设计的发布/订阅消息协议。它的特点是协议轻量、报文头最小可压缩到2字节,支持QoS分级,支持遗嘱消息和保留消息,天生适合设备端接入。

而Kafka、RabbitMQ这类消息中间件,重心是服务端之间的高吞吐、可靠投递、流式处理。你说用RabbitMQ的MQTT插件行不行?行,但在海量设备接入、弱网环境、长连接保活这些场景下,原生的MQTT Broker才更得心应手。我见过有团队用Kafka直接接设备数据,结果小设备频繁断连重连,Kafka的producer把Broker搞得压力山大。选型错误不是因为工具不好,而是没搞清楚边界。

1.2 Broker选型:本地用Mosquitto,生产可以考虑EMQX

Spring Boot对接MQTT,真正连的是MQTT Broker。Broker的选型直接影响后面的开发体验和运维成本。我列一个自己用过的对比:

Broker适合场景协议支持上手难度备注
Eclipse Mosquitto本地开发、小规模设备接入MQTT 3.1/3.1.1/5.0极低单机轻量,Windows/Linux包都有,配置文件简单
EMQX生产环境、海量设备、集群部署MQTT 3.1/3.1.1/5.0、MQTT-SN、CoAP中Dashboard好用,规则引擎强大,有开源版
HiveMQ企业级、需要安全与扩展MQTT 3.1/3.1.1/5.0中商业支持完善,本地开发可以用HiveMQ Community Edition
RabbitMQ已经有RabbitMQ体系的团队通过插件支持MQTT中不做主推,节点压力偏大

本地开发我几乎只用Mosquitto。掉线重连、遗嘱消息这些特性都能在本地模拟,部署也简单。服务端上线阶段再切EMQX,配置思路基本一致,代码层面可以做到完全无感切换,这一步做得好,后面运维会省很多心。

2. 依赖引入与配置落盘:把连接参数从代码里赶出去

2.1 Maven依赖怎么加才不踩Spring Boot版本坑

Spring Boot本身不带MQTT客户端库,所以核心依赖是Eclipse Paho Java Client。如果你用的是Maven,直接在pom.xml里加:

<dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse.paho.client.mqttv3</artifactId> <version>1.2.5</version> </dependency>

这个库很干净,是纯Java实现,不依赖Spring框架,所以不管你是Spring Boot 2.x还是3.x都能用。踩过坑的人会告诉你,千万别图省事把spring-integration-mqtt也一起引进来。它有它的用处,但对大多数项目来说,它会把你包装进一套Spring Integration的MessageChannel体系里,调试消息流转时多了一层“魔法”。我更倾向直接用Paho原生的MqttClient,所有事情透明可控。等你的消息路由真的复杂到需要Channel处理,再考虑集成层不迟。

如果你用的是Spring Boot 3.x,注意JDK版本至少17,Paho库不受影响。真正容易出问题的是传递依赖冲突,比如项目中已经有io.netty:netty-all,但Paho会用JDK自带的Socket实现,两者不冲突。倒是如果你引入了一些物联网平台SDK时,要留意它们是否自带老版本Paho,建议统一看下依赖树:

mvn dependency:tree -Dincludes=org.eclipse.paho

2.2 application.yml里的配置怎么设计

千万不要把Broker地址、用户名密码硬编码到Java类里。第一是不同环境要切地址,第二是密码进了代码仓库就有泄露风险。我会在application.yml里单独维护一段配置:

spring: mqtt: broker-url: tcp://localhost:1883 client-id: springboot-service-001 username: device_user password: device_pass connect-timeout: 5 keep-alive: 60 clean-session: false auto-reconnect: true topic: subscribe: demo/devices/up qos: 1

有个细节想特别提一下:clientId一定不能硬编码成固定的。我见过一个事故,测试环境两台实例用同一个clientId连同一个Broker,结果设备数据忽上忽下,两台实例互相踢下线。实际生产做法是clientId = 服务名 + 实例IP或随机数,保证每个进程唯一。

这边用@ConfigurationProperties绑定配置,干净也好维护:

@Component @ConfigurationProperties(prefix = "spring.mqtt") public class MqttProperties { private String brokerUrl; private String clientId; private String username; private String password; private int connectTimeout = 5; private int keepAlive = 60; private boolean cleanSession = true; private boolean autoReconnect = true; // getter / setter 省略 }

2.3 先把Broker跑起来:本地一条命令的事

如果你用的是Windows,去Mosquitto官网下安装包,装完安装目录里就有mosquitto.exe和mosquitto.conf。启动前先确认一下mosquitto.conf里监听了1883端口,默认配置就带,直接跑:

mosquitto -v

Linux上用Docker最快:

docker run -d --name mosquitto -p 1883:1883 eclipse-mosquitto:2.0

注意Mosquitto 2.x默认不允许匿名访问,如果本地测试懒得配置用户名密码,可以进容器改一下mosquitto.conf,加一句allow_anonymous true。查连接情况推荐用mosquitto_sub命令行工具,比如订阅某个topic看消息通没通:

mosquitto_sub -t demo/devices/up -v

工具方面,MQTT Explorer是图形化的调试利器,能看到所有topic和消息内容,连上后还能直接手动发消息。断点调试时它比命令行直观很多。

3. 核心代码架构:让MQTT客户端成为Spring容器中的活体组件

3.1 为什么不能直接在Controller里new一个MqttClient

刚接触MQTT的人容易把MqttClient当成普通工具类,用的时候new一个,不用就丢了。这在生产环境是致命的。MqttClient底层维护着TCP长连接、心跳线程、消息分发线程,如果反复创建和销毁,连接根本来不及稳定,Broker端也会积累大量会话残留,内存和文件描述符都会被耗尽。

正确做法是:把MqttClient设计成Spring容器中的单例Bean,由Spring负责它的创建和销毁,项目启动时连接一次,整个生命周期复用。这也是标题里“对接”二字的真正含义——不是写个能收发消息的demo,而是让MQTT连接作为基础组件融入Spring Boot应用。

3.2 用@Configuration管理MqttClient的完整生命周期

我直接给一份可用的配置类代码,核心逻辑都在注释里说明白:

@Configuration public class MqttClientConfig { @Bean(destroyMethod = "close") public MqttClient mqttClient(MqttProperties properties) throws MqttException { MqttClient client = new MqttClient( properties.getBrokerUrl(), properties.getClientId(), new MemoryPersistence() ); MqttConnectOptions options = new MqttConnectOptions(); options.setAutomaticReconnect(properties.isAutoReconnect()); options.setCleanSession(properties.isCleanSession()); options.setConnectionTimeout(properties.getConnectTimeout()); options.setKeepAliveInterval(properties.getKeepAlive()); if (StringUtils.hasText(properties.getUsername())) { options.setUserName(properties.getUsername()); options.setPassword(properties.getPassword().toCharArray()); } client.connect(options); return client; } }

几个容易忽略的点逐个说。

MemoryPersistence()是客户端本地持久化策略。它把QoS 1和QoS 2的待确认消息存在内存里,应用重启会丢。如果你要求严格不丢消息,可以换成MqttDefaultFilePersistence,指定一个目录存消息。对大多数业务场景,MemoryPersistence够用了。

destroyMethod = "close"这句话很关键。Spring在关闭容器时会自动调MqttClient.close(),把连接、心跳线程都释放干净。如果没有这一步,应用重启时旧连接不会主动断开,Broker端会堆积残留会话,新实例起来后可能因为clientId冲突被互踢,这问题排查起来很折磨人。

setAutomaticReconnect(true)最实用。Paho内置了重连机制,当断网或者Broker重启时,它会在后台自动用原参数重连,不用自己写while循环。但要注意:自动重连成功后,你之前subscribe的topic可能会“丢”。cleanSession=false时Broker会恢复持久订阅,但如果cleanSession=true,重连后就要主动重新订阅一遍。而这恰恰是那位让我写这篇分享的兄弟踩的坑——他加了自动重连,却忘了重订阅,结果每次Broker重启,应用都在线但收不到数据。

3.3 封装MqttGateway:让业务代码只管调方法

MqttClient已经成了Spring Bean,但我不建议在业务代码里直接注入MqttClient去调publish,因为调用方容易搞错topic、qos、payload这些参数。我会封装一个MqttGateway,向上提供业务友好的方法:

@Service public class MqttGateway { private final MqttClient mqttClient; public MqttGateway(MqttClient mqttClient) { this.mqttClient = mqttClient; } public void publish(String topic, String payload, int qos) { MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8)); message.setQos(qos); mqttClient.publish(topic, message); } public void publish(String topic, Object payload, int qos) { String json = JSON.toJSONString(payload); publish(topic, json, qos); } }

这样业务层写mqttGateway.publish("demo/devices/down", commandVO, 1)就完成了一次下发,不用关心字节数组、字符集这些细节。字符集这里特别提一句,设备端五花八门,有些固件默认GBK编码,你统一用UTF-8发送,对方解出来就是乱码。跟设备厂商约定好编码格式,比在代码里到处兼容强得多。

4. 消息的来与去:订阅、发布与业务回调解耦的完整落地

4.1 发布消息:同步阻塞还是异步发送

MqttClient.publish本身是同步方法,它会等Broker的确认(QoS>0时)后才返回。设备量小的时候没啥感觉,但如果你在一个高并发的HTTP接口里同步发消息,线程会阻塞在IO等待上,接口吞吐量会明显下降。我通常把发布动作放到异步线程池里执行,或者用MqttAsyncClient。

Paho还提供MqttAsyncClient,它和MqttClient的API风格几乎一样,只是方法名多了Async。实际项目中如果你只是偶尔下发指令,MqttClient就行。如果下发频率高,比如每分钟几百条设备指令,还是用MqttAsyncClient并在回调里把失败消息记日志或投递到补偿表。这里给大家一个判断标准:QoS 1的同步发布,单条耗时大约5到15毫秒(本地网络),如果你的业务单位时间内需要发布超过200条,不使用异步就要仔细掂量了。超过这个量级,线程阻塞时间都是肉眼可见的。

4.2 订阅与回调:消息从网络到达业务代码的全链条

订阅的前提是设置回调。MqttCallback是Paho的核心接口,它有三个方法:

  • connectionLost(Throwable cause):连接丢失时触发。如果automaticReconnect没开或重连失败,这里是你做告警和补偿的唯一机会。
  • messageArrived(String topic, MqttMessage message):每条消息到客户端时触发。
  • deliveryComplete(IMqttDeliveryToken token):消息发布到达Broker后触发(仅限QoS>0)。

注册回调后,用subscribe订阅topic:

@Configuration public class MqttSubscriberConfig { @Bean public MqttCallback mqttCallback(MqttClient mqttClient, MqttMessageRouter router) { MqttCallback callback = new MqttCallback() { @Override public void connectionLost(Throwable cause) { log.error("MQTT connection lost", cause); } @Override public void messageArrived(String topic, MqttMessage message) throws Exception { router.route(topic, message); } @Override public void deliveryComplete(IMqttDeliveryToken token) { // 可在这里记录发布成功的日志 } }; mqttClient.setCallback(callback); try { mqttClient.subscribe("demo/devices/up", 1); } catch (MqttException e) { throw new IllegalStateException("subscribe failed", e); } return callback; } }

在messageArrived里直接做业务解析是很多demo的做法,但我强烈不建议。原因很简单:这个回调线程是Paho内部的消息分发线程,如果业务处理慢,消息会阻塞在里面,后续所有消息都堵住,QoS 2场景下还会导致Broker重发。正确做法是回调里只做消息路由,立刻把任务交给业务线程池。

deliveryComplete是很多人忽略的一点。如果你用的是QoS 1或QoS 2并发了大量消息,可以在其中实现类似“发布成功计数”或“待确认清理”的逻辑,方便测试时判断链路是否完整。

4.3 用线程池隔离业务处理:防止回调被慢SQL拖死

我实现了一个很简单的MqttMessageRouter,它做的事情只有一件:根据topic前缀找到对应处理器,包装成Runnable丢进线程池。

@Component public class MqttMessageRouter { private final ExecutorService bizExecutor = Executors.newFixedThreadPool(8); private final Map<String, TopicHandler> handlerMap; public MqttMessageRouter(Map<String, TopicHandler> handlerMap) { this.handlerMap = handlerMap; } public void route(String topic, MqttMessage message) { // 按topic精确或前缀匹配handler TopicHandler handler = handlerMap.entrySet().stream() .filter(e -> StrUtil.startWithIgnoreCase(topic, e.getKey())) .map(Map.Entry::getValue) .findFirst() .orElse(defaultHandler()); bizExecutor.execute(() -> handler.handle(topic, message)); } }

线程池大小怎么定?我一般按设备量和消息频率算。假设每秒峰值消息500条,每条处理耗时20毫秒,一个线程每秒能处理约50条,理论最少需要10个线程。再留一些余量应对突发,8到16个比较均衡。线程池建议单独命名,方便jstack排查问题时识别:

ThreadFactory factory = new ThreadFactory() { private final AtomicInteger seq = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "mqtt-biz-thread-" + seq.incrementAndGet()); t.setDaemon(true); return t; } };

4.4 一次具体的设备数据上报落地案例

以水表采集器为例,设备端定时通过MQTT上报demo/devices/up,payload是一段JSON,包含设备编号、瞬时流量、累计用量等字段。我在路由器里找到对应handler,解析出DeviceMeterData对象,校验设备编号,写库,更新缓存。整个过程流水线一样走下来:

@Component("demo/devices/up") public class DeviceDataHandler implements TopicHandler { @Override public void handle(String topic, MqttMessage message) { String payload = new String(message.getPayload(), StandardCharsets.UTF_8); DeviceMeterData data = JSON.parseObject(payload, DeviceMeterData.class); // 幂等校验:同一个设备同一采集周期只处理一次 // 写库、更新缓存、触发后续业务流程 } }

注意这里的幂等校验。MQTT QoS 1存在“可能重复投递”的语义,设备端和Broker之间重发机制会让同一条消息出现在你面前两次。重复消息不是异常,是MQTT协议的正常行为。你的业务处理必须天然支持幂等,这个在架构设计时就要想清楚。

5. 从能跑到能生产:心跳、重连、QoS、会话延续这些硬骨头必须啃

5.1 客户端ID冲突:最隐蔽的连环坑

前面提过一次clientId固定引发的故障,这里再展开讲讲机制。MQTT协议规定,同一个Broker上,两个相同clientId的客户端同时在线时,后连上的会把先连上的踢下线,也就是所谓的“互相顶替”。如果你部署了两份Spring Boot实例,又把clientId写死,那么每次实例启动,另一台就掉线,自动重连后又把对方顶下去,如此循环,日志里全是Connection lost和Unexpected disconnect混在一起,非常折磨人。

解决方式也不复杂:

String clientId = "device-service-" + InetAddress.getLocalHost().getHostAddress().replace(".", "-") + "-" + UUID.randomUUID().toString().substring(0, 8);

生产环境我一般把机器IP替换成注册中心分配的实例ID,保证唯一且可读。

5.2 心跳、自动重连、手动补订:三层保障缺一不可

keep-alive间隔决定客户端向Broker发送心跳报文的频率。默认60秒,意味着Broker超过一定时间没收到任何报文就会判客户端离线。如果你的设备网络极差,我建议把keep-alive调小到15到30秒,让Broker尽快感知离线。Paho底层有setKeepAliveInterval,单位是秒,可以在MqttConnectOptions里配置。

自动重连有三个层面的保障:

  • Paho内部重连机制:setAutomaticReconnect(true),断网后自动建立连接;
  • 应用层兜底:如果重连失败次数超过N次,记录日志并发送告警,人工介入;
  • 重连成功后的订阅恢复:这是最容易被忽略的环节。因为Paho的自动重连只管连接,不管订阅。你必须在connectionLost后等到连接恢复的那一刻重新subscribe。一个常见做法是监听MqttCallbackExtended,里面有个connectComplete(boolean reconnect, String serverURI)方法。如果reconnect为true,就重新订阅。
public class MqttCallbackHandler implements MqttCallbackExtended { private final MqttClient mqttClient; @Override public void connectComplete(boolean reconnect, String serverURI) { if (reconnect) { try { mqttClient.subscribe("demo/devices/up", 1); log.info("MQTT reconnected and re-subscribed"); } catch (MqttException e) { log.error("re-subscribe fail", e); } } } }

5.3 QoS 0、1、2到底该怎么选

很多初学者以为QoS越高越安全,结果全部用2,代价是性能翻倍下降。我直接给一张选型表:

QoS语义代价典型场景
QoS 0至多一次,可能丢最低,无确认高频遥测数据、温度湿度等允许丢失的指标
QoS 1至少一次,可能重复中等,一条消息一条ACK设备上报、指令下发,大部分业务选它
QoS 2恰好一次,四步握手最高,协议开销翻倍极端重要的消息,如金额流水、鉴权指令

我记得有一次对接抄表系统,厂商坚持用QoS 2,说“保证不丢”。结果压测时Broker负载飙高,消息吞吐掉了一半。后来跟他约定了业务层面幂等兜底,改用QoS 1,效果立竿见影。真正的可靠性从来不是靠单一QoS实现的,而是业务幂等 + QoS 1 + 对账补偿。

5.4 retained消息和遗嘱消息:容易被忽略但极其有用的两个开关

retained消息:发布时把retained标志设为true,Broker会保存这条topic的最后一条消息。当新的订阅者订阅这个topic时,会立刻得到这条保留消息。适合用来下发设备的最新配置状态。比如设备重连后上线,并不需要等服务器主动推配置,订阅配置topic后,Broker自动把最新配置发给它。注意,retained消息只有一个,新消息会覆盖旧消息。

遗嘱消息(Last Will and Testament,简称LWT):客户端在连接时携带一条“遗嘱”,当客户端非正常离线(比如断网、崩溃)时,Broker会代替客户端把这封遗嘱发出去。这个机制在做设备在线状态管理时非常好用。比如设备订阅了demo/devices/{id}/status,服务端让它在上线时发布一条online遗嘱,Broker会在设备掉线时自动发布offline消息,服务器不用轮询就能感知设备状态变化。

Paho里设置遗嘱消息的方式:

MqttTopic willTopic = client.getTopic("demo/devices/001/status"); MqttMessage willMessage = new MqttMessage("offline".getBytes()); willMessage.setQos(1); options.setWill(willTopic, willMessage.getPayload(), willMessage.getQos(), true);

5.5 常见异常的完整排查思路

现象可能原因处理方式
Connection refused - connection refusedBroker没启动、端口错误、防火墙拦截telnet 测试1883端口连通性,检查防火墙;用MQTT Explorer手动连一下做对照
Connection lost (32109)网络抖动、Broker重启、心跳超时看Broker端日志,开启automaticReconnect,确认keep-alive配置合理
消息偶尔收不到订阅topic匹配错误、cleanSession=true导致离线消息丢失、QoS 0丢弃用mosquitto_sub -v观察原生的topic消息;查看是否用了通配符;确认重连后已重新订阅
接收消息重复QoS 1的重复投递、设备端重发业务幂等处理,不依赖协议层去重
中文乱码字符集不一致确认收发两端统一UTF-8,检查固件编码
客户端被踢下线clientId冲突检查所有实例的clientId是否唯一,将固定值改为动态生成

6. 变成项目里真正可维护的基础设施:从“能跑”到“用得舒服”

6.1 Topic命名规范:一上来就要定好的秩序

Topic设计得乱,后面业务会越来越痛苦。我的建议是采用层级化的命名空间,按“域/设备类型/设备ID/数据类型”来设计。比如:

demo/devices/{deviceId}/up # 设备上报 demo/devices/{deviceId}/down # 服务端下发 demo/devices/{deviceId}/status # 设备在线状态 demo/alarm/{deviceId}/up # 设备告警

订阅时可以用MQTT通配符:demo/devices/+/up订阅所有设备的上报,demo/devices/001/#订阅某个设备的所有消息。注意:通配符在发布时是禁止的,你只能订阅时用。这是协议规定,也是很多人初次调试时遇到的问题。

我记得有一个项目,设备厂商把设备ID直接写在topic里,消息量大后,运维想按设备批次订阅,却发现topic层次设计得很差,只能全部接收再程序过滤。后来推广了一套统一规范,接入新设备时直接按模板走,省掉了大量定制逻辑。

6.2 用Spring Event解耦:消息进来后交给业务各取所需

MqttMessageRouter把MQTT消息转成Runnable跑在线程池里。线程池的handler再往里走一步,就很适合引入Spring的事件机制。handler只负责解析和校验,然后发布一个DeviceDataReceivedEvent,真正关心这个事件的业务模块通过@EventListener监听。这样一来,MQTT接入层不依赖任何具体业务,新增一个数据处理模块时也不用改MQTT相关代码。

public class DeviceDataReceivedEvent extends ApplicationEvent { private final DeviceMeterData data; // 构造方法 }
@Component public class DeviceDataEventHandler { @EventListener public void onDeviceData(DeviceDataReceivedEvent event) { // 存储、告警、转发、缓存刷新等业务逻辑 } }

这个设计在设备接入量变大、业务逻辑和生产环境需要解耦的时候非常受益。

6.3 把MQTT连接状态暴露给Spring Boot Actuator

生产环境调试问题的时候,第一件事是确认MQTT连接是否还活着。利用MqttClient.isConnected(),自定义一个HealthIndicator,可以让MQTT连接状态直接通过Actuator暴露出来:

@Component public class MqttHealthIndicator implements HealthIndicator { private final MqttClient mqttClient; public MqttHealthIndicator(MqttClient mqttClient) { this.mqttClient = mqttClient; } @Override public Health health() { boolean connected = mqttClient.isConnected(); if (connected) { return Health.up() .withDetail("broker", mqttClient.getServerURI()) .build(); } return Health.down().withDetail("reason", "mqtt disconnected").build(); } }

配合Spring Boot Actuator,/actuator/health端点会返回MQTT连接状态。在报警平台或者K8s探活里加上这个检查,MQTT连接断开时服务能第一时间被发现。

6.4 更进一步:SSL加密、多Broker切换和共享订阅

如果你的业务涉及跨公网的设备接入,强烈建议开启TLS/SSL,不要让设备明文把数据报上来。Broker端启用SSL后,Spring Boot侧代码只改一处:broker-url从tcp://变成ssl://,然后给MqttConnectOptions设置信任证书:

Properties sslProps = new Properties(); sslProps.setProperty("com.ibm.ssl.trustStore", "/path/to/truststore.jks"); sslProps.setProperty("com.ibm.ssl.trustStorePassword", "your-pass"); options.setSSLProperties(sslProps);

多Broker切换,我一般通过Spring Profile实现。开发、测试、生产各维护一份application-{env}.yml,只换broker-url、client-id和账号。代码层面不需要任何改动。如果哪一天设备量涨到单机Broker撑不住,把url换成EMQX集群的负载均衡地址就行。

共享订阅是MQTT 5.0引入的特性,在EMQX这类Broker下,多个服务实例订阅同一个topic时,消息会在实例之间轮询分发。如果你部署了多个Spring Boot实例消费设备数据,开启共享订阅后不需要自己处理负载均衡:

sub: demo/devices/up # 普通订阅,所有实例都会收到同一份消息 $share/group1/demo/devices/up # 共享订阅,消息在group1内轮询消费

如果是多个实例同时消费并且业务上要求必须“每个实例都收到”,就用普通订阅;如果要分摊消息处理压力,就用共享订阅。这个选择直接决定你的设备状态会不会出现重复计算。

写在最后的个人体会

Spring Boot对接MQTT,表面看是引入一个客户端库、写几个配置类的事,实际把连接管理、订阅恢复、消息路由、并发隔离、幂等处理全部做到位,是一个系统工程。我自己最初也经历了“demo能跑就行”的阶段,直到生产环境被clientId冲突和重连丢订阅两件事教育了,才老老实实把生命周期和故障恢复补齐。

最后分享一个自查清单:连接由Spring管理了吗?clientId保证唯一了吗?清了session之后还会不会丢订阅?回调里有没有做线程池隔离?业务处理幂等了吗?QoS选的是不是性价比最高的档位?这五条都过一遍,你的Spring Boot集成MQTT才算真正具备上线条件。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询