mqtt断开7秒钟,订阅全部消失,这是为啥
客户端设置了记住客户端状态,会话过期时间为120
我尝试重新订阅 也无法订阅
options.setWill(“WillTopic”, message);
|||options.setCleanStart(false);
|||options.setSessionExpiryInterval(120L);
|||options.setKeepAliveInterval(MqttConfig.getKeepAliveInterval());
|||options.setConnectionTimeout(MqttConfig.getTimeout());
|||options.setMaxReconnectDelay(60);
|||options.setServerURIs(new String { MqttConfig.getBroker() });
|||options.setAutomaticReconnect(true);
|||options.setAutomaticReconnectDelay(10, 60);
订阅大概率没丢,是会话恢复后你又用同一批 Subscription Identifier 重新订阅了。
截图里重连后 Clean Start=false,订阅数还有 425;日志里的 The Subscription Identifier ... already exists 也说明 EMQX 侧还保存着原订阅/订阅标识,不是“断 7 秒后清空”。
你现在是 MQTT v5、Clean Start=false、Session Expiry=120,同一个 Client ID 在 120 秒内重连会恢复原会话。这时不要无条件重新订阅。先看 CONNACK 的 Session Present:为 true 就跳过全量订阅;为 false 才订阅。
客户端逻辑改成这样:
options.setCleanStart(false);
options.setSessionExpiryInterval(120L);
// reconnect/connect success 后
if (!connAckSessionPresent) {
subscribeAll();
}
如果当前 Java 库拿不到 Session Present,临时规避是重连后不要带 Subscription Identifier 重新订阅,或者每次给每个订阅生成新的 identifier,别复用已经存在的那批 ID。
Dashboard 上断线时看不到在线客户端/订阅,不能直接当成会话被删。用同一个 clientId 重连后,看连接详情里的 Clean Start=false、会话是否 resumed,以及消息是否还能按原 topic 下发。
我之前是在连接后 进行订阅,重连不订阅,但是订阅还是没了
@Override
public void connectComplete(boolean reconnect, String serverURI) {
log.info("连接完成 " + serverURI + " " + reconnect);
if (!reconnect) {
// 订阅
initSubscribe();
}
}
那么理论上来说 这么写是对的
如果我不保留会话,使用同一clientId 重连 再订阅 也是可以的吧
如果订阅没有丢,我重新订阅报错了,原来的订阅也会消失吗?
现在的情况是 订阅确实没了
connectComplete(boolean reconnect, ...) 不能当成“会话已恢复”的判断。
reconnect=true 只说明这是客户端库自动重连,不等于 EMQX 返回了旧会话。MQTT 5 要看 CONNACK 里的 Session Present:
Session Present=true:旧会话还在,订阅应还在,可以跳过全量订阅。Session Present=false:服务端没有恢复旧会话,这次是新会话,必须重新订阅。
所以逻辑不要只写成if (!reconnect) subscribe()。如果客户端库拿不到Session Present,先用最稳的方式处理:每次连接成功都订阅一遍,但不要复用固定的Subscription Identifier。
@Override
public void connectComplete(boolean reconnect, String serverURI) {
log.info("连接完成 {} reconnect={}", serverURI, reconnect);
initSubscribe(); // 先去掉固定 Subscription Identifier,或保证每个订阅 ID 唯一
}
同一个 clientId 重连后再订阅是可以的。已有同一 topic filter 时,SUBSCRIBE 应该是覆盖/更新订阅参数,不应该把原订阅删掉。Subscription Identifier already exists 说明这次 SUBSCRIBE 被拒了,正常情况下不会因为这次失败把旧订阅清空。
如果 Dashboard 里确实变成 0 个订阅,优先查这几项:
- 重连前后
clientId是否完全一样; - CONNACK 里的
Session Present是 true 还是 false; - 客户端实际发出的 CONNECT 里
Clean Start和Session Expiry Interval是多少; initSubscribe()里是否给不同 topic 复用了固定Subscription Identifier;- 是否有同
clientId的第二个连接接管了会话。
先把重连那次 CONNECT/CONNACK 日志、SUBSCRIBE reason code、initSubscribe()里设置Subscription Identifier的代码贴出来。现在看更像是“会话没有被恢复,但代码因为reconnect=true跳过了订阅”,或者“订阅时复用了固定 Subscription Identifier 导致 SUBSCRIBE 失败”,不是 EMQX 主动把已有订阅删了。
1 clientId 完全一样
2 不清楚
3
|||options.setCleanStart(false);
|||options.setSessionExpiryInterval(120L);
4 不同 topic 复用了固定 Subscription Identifier
MqttProperties mqttProperties = new MqttProperties();
List subscriptionIdentifiers = new ArrayList<>();
Integer property = (mqttSubscription.getTopic()).hashCode();
property = Math.abs(property) % 268435455;
subscriptionIdentifiers.add(property);
mqttProperties.setSubscriptionIdentifiers(subscriptionIdentifiers);
client.subscribe(mqttSubscription, “”, null, mqttTopicListener, mqttProperties);
5 不清楚
这个是当时的现场日志
从这看 重连之后进行了订阅,之前是这样写的
@Override
public void connectComplete(boolean reconnect, String serverURI) {
log.info("连接完成 " + serverURI + " " + reconnect);
// 订阅
initSubscribe();
}
基本明确了:旧会话恢复后,原来的订阅还在,但代码又用同一个 Subscription Identifier 重新订了一遍,所以 EMQX 拒绝了这次 SUBSCRIBE。
你这里不是“不同 topic 共用同一个 ID”这么简单,而是每个 topic 用 hashCode() 算出固定 ID。会话恢复后,服务端还保存着这个 topic 的旧订阅和旧 Subscription Identifier;重连后再次订阅同一个 topic、带同一个 ID,就会触发日志里的:
The Subscription Identifier ... already exists
先按这个方式改,别先怀疑 EMQX 把订阅删了:
// 临时排障:先不要设置 Subscription Identifier
// mqttProperties.setSubscriptionIdentifiers(subscriptionIdentifiers);
client.subscribe(mqttSubscription, "", null, mqttTopicListener, mqttProperties);
如果业务确实需要 Subscription Identifier,就不要用固定 topic hash 直接复用。要么在确认 Session Present=false 时才全量订阅,要么给“本次新建的订阅”分配当前会话内没被占用的 ID。Session Present=true 时,旧订阅本来就应该继续用,不要重复全量订阅。
另外,SUBSCRIBE 被 Subscription Identifier already exists 拒绝,正常不会主动删除旧订阅。如果 Dashboard 里看到订阅数变 0,继续查这三项:
- CONNACK 里的
Session Present到底是 true 还是 false; - 有没有第二个相同
clientId的连接把会话顶掉; - 断线和重连是否落到不同节点,且旧会话没有被恢复。
先把Session Present打出来;如果客户端库暂时拿不到,就先去掉 Subscription Identifier 跑一轮验证。

