ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MQTT 客户端反复重连怎么办?Spring Boot 接 EMQX 断线重连踩坑实录

MQTT 客户端反复重连怎么办?Spring Boot 接 EMQX 断线重连踩坑实录 先看一段真实的事故现场下面这段日志摘自我自己项目的真实运行日志app-run.log一字未改08:47:53.028 INFO --- [main] 正在连接 MQTT Broker: tcp://localhost:1883 08:47:53.695 WARN --- [thub-server-001] MQTT 连接被拒绝: 无权连接 08:48:03.610 INFO --- [scheduling-1] 正在连接 MQTT Broker: tcp://localhost:1883 08:48:04.443 INFO --- [thub-server-001] MQTT 连接成功 08:48:04.451 INFO --- [thub-server-001] MQTT 已订阅: up//, st// (reconnectfalse)注意第二行的线程名[thub-server-001]——这是 Paho 客户端自己的连接线程第三行的[scheduling-1]是 SpringScheduled定时任务线程池的线程。两条正在连接来自两个不同的发起方这正是很多 MQTT 连接抖动问题的根源不止一个人在抢着连。MQTT 客户端反复重连的四种典型原因按我踩过和见过的频率排序1. 手动重连和自动重连打架最容易踩setAutomaticReconnect(true)开了自动重连又自己起一个定时任务轮询调connect()。Paho 的自动重连线程和你手动发起的连接并发抢同一个 client——同一个 clientId谁连上谁把对方踢下线MQTT 同 clientId 互踢规则表现出来就是连接成功 → 立刻断开 → 再连接的死循环。2. 同一个 clientId 被顶号两个进程比如本地调试的服务没关、又启动了一个用了同一个 clientId 连同一个 Broker双方互相踢表现和第 1 种几乎一样。排查方法打开 EMQX Dashboard → 监控 → 客户端看同一个 clientId 是不是频繁上下线、或者出现两条会话记录。3. 认证 / ACL 被拒密码错、HTTP 认证回调返回格式不对EMQX 要求{result:allow}返回自己的统一响应体会被一律判拒、ACL fail closed。日志特征是稳定报连接被拒绝——这种不会抖动是匀速失败每 10 秒被拒一次那种。4. Broker 压根没起来应用比 EMQX 先启动是开发机的常态电脑重启后启动顺序不定。特征是连接超时异常而不是被拒绝。第 3、4 种都好办真正让人摸不着头脑的是 1 和 2——因为它们的表现是明明连上了怎么又断了。我的踩坑与修复重连权收归一家我的第一版就是第 1 种automaticReconnect开着同时又写了个Scheduled任务每 10 秒检查连接、没连上就connect()。平时没事一旦断线两个重连机制同时启动互相把对方踢下线日志里连接成功和连接断开交替刷屏。修复思路就一句话重连权只能有一个主人且分阶段交接。还没连上过应用刚启动、Broker 还没就绪→ 定时任务兜底重试成功连上过一次之后 → 定时任务永久退场断线重连完全交给 Paho真实代码如下来源iot-device-platform/src/main/java/com/iothub/mqtt/MqttConnection.java/** 只要成功连上过一次后续重连就完全交给 Paho 的 automaticReconnect */privatevolatilebooleaneverConnectedfalse;/** 上次发起连接的时间防止定时任务和自动重连同时抢同 clientId 会被 EMQX 互踢 */privatevolatilelonglastConnectAttempt0;/** 只在从来没连上过时兜底连上过之后 automaticReconnect 负责一切 */Scheduled(fixedDelayString${mqtt.reconnect-interval-ms:10000})publicvoidensureConnected(){if(everConnected){return;}connect();}privatesynchronizedvoidconnect(){if(client!nullclient.isConnected()){return;}longnowSystem.currentTimeMillis();if(now-lastConnectAttemptprops.getReconnectIntervalMs()){return;// 节流两次连接尝试强制间隔防止高频轰炸 Broker}lastConnectAttemptnow;try{if(clientnull){clientnewMqttAsyncClient(props.getBroker(),props.getClientId(),newMemoryPersistence());client.setCallback(callback());}MqttConnectOptionsoptionsnewMqttConnectOptions();options.setAutomaticReconnect(true);// 连上之后掉线交给 Pahooptions.setCleanSession(true);options.setMaxInflight(100);options.setConnectionTimeout(10);// ... 用户名密码略client.connect(options,null,newIMqttActionListener(){OverridepublicvoidonSuccess(IMqttTokenasyncActionToken){log.info(MQTT 连接成功);}OverridepublicvoidonFailure(IMqttTokenasyncActionToken,Throwableexception){lastConnectAttempt0;// 被拒后清零时间窗下一轮定时任务可以立刻重试log.warn(MQTT 连接被拒绝: {},exceptionnull?unknown:exception.getMessage());}});}catch(MqttExceptione){log.warn(MQTT 连接失败{}{}ms 后重试,e.getMessage(),props.getReconnectIntervalMs());}}配套的回调里connectComplete负责交接OverridepublicvoidconnectComplete(booleanreconnect,StringserverURI){everConnectedtrue;// 从此定时任务退场重连交给 Pahotry{String[]topics{props.getTelemetryTopic(),props.getStatusTopic()};int[]qos{1,1};IMqttTokentokenclient.subscribe(topics,qos);token.waitForCompletion(10_000);log.info(MQTT 已订阅: {} (reconnect{}),String.join(, ,topics),reconnect);}catch(MqttExceptione){log.error(MQTT 订阅失败,e);}}三个设计点面试也能聊为什么不启动时连不上就抛异常应用先起、Broker 后起是常态。服务不因依赖缺失而起不来连接失败只告警由定时任务保证最终连上——这正是最终成功的思路。为什么重连后必须重新订阅我用了cleanSessiontrue会话不保留重连后订阅关系是空的不在connectComplete里补订阅就出现连接显示在线、数据一条都收不到的灵异现象。everConnected为什么要 volatile定时任务线程和 Paho 回调线程是两个线程一个写一个读可见性必须保证。怎么验证重连真的可靠光看代码没用重连这种东西必须现场演练一次。我的验证步骤可复现起应用等日志出现MQTT 已订阅EMQX Dashboard 客户端页能看到iothub-server-001在线bin\emqx stop停掉 Broker → 应用日志出现MQTT 连接断开connectionLostbin\emqx start再启动不重启应用→ 日志自动出现MQTT 已订阅: … (reconnecttrue)回到看板实时曲线继续往前走数据链路无感恢复。排查清单拿去直接用现象大概率原因怎么确认稳定报连接被拒绝认证/ACL 配置错核对用户名密码HTTP 认证回调必须返回{result:allow}疯狂刷正在连接连上又断手动重连与自动重连互踢全局搜connect(确保只有一个地方发起重连连接成功几秒后被踢循环同 clientId 顶号EMQX Dashboard 看该 clientId 是否频繁上下线查有没有第二个进程启动时报连接失败之后正常Broker 比应用后启动属正常定时任务兜底即可显示在线但收不到数据重连后没重新订阅在connectComplete里补订阅三个常见追问QPaho 自动重连的间隔能自己控制吗A不能配置它内部从 1 秒开始翻倍、2 分钟封顶。想要可控的退避节奏就得关掉automaticReconnect自己实现——但切记全工程只留这一个重连入口别像第一版的我那样两头抢。QcleanSession 该设 true 还是 falseA看你要不要断线补传。我的服务端消费遥测数据丢了靠设备端本地缓存 时间戳重连后补发服务端按时间戳幂等去重所以true更简单。要 Broker 帮忙暂存离线消息就得false代价是会话状态管理复杂度上一个台阶。Qkeepalive 设多少合适APaho 默认 60 秒服务端固定网络够用弱网设备可以适当加大配合 Broker 侧的会话过期时间一起调。keepalive 不是越小越实时它只是探活实时性靠的是上报频率和推送链路。我是软件工程在读专升本正在从零搭一个物联网设备接入平台Spring Boot EMQX MySQL本文代码全部来自这个真实项目。踩坑记录会持续更新欢迎关注一起卷物联网后端。
返回列表