MQTT之重复消息(1、导致重复消息的因素)

背景:
        在 Spring Boot + MQTT 5.0 环境中,设备 A 和设备 B 重复接收消息的底层原因涉及协议设计、网络传输和 Broker 行为的协同作用。我们通过具体场景和代码示例来揭示其原理。该文章主要描述了产生重复消息的主要原因和本质问题,下一篇文章分析消息在发送、消息订阅整个链路过程中可能产生重复的关键点。

1. MQTT协议特性导致的重复(本质原因

1.1 QoS机制引发
  • QoS 1(至少一次)

    • Broker未及时返回PUBACK时,设备会重发消息

    • 重发间隔通常为5-15秒(依赖实现)

    • // 设备端发布消息(QoS 1)
      mqttClient.publish("device/123/data", payload, 1, false);
    • 时序:sequenceDiagram
          participant Device
          participant Broker
          Device->>Broker: PUBLISH (PacketID=123)
          Broker--x Device: PUBACK (因网络延迟500ms)
          Note over Device: 在300ms超时后重发
          Device->>Broker: PUBLISH (PacketID=123 重传)
          Broker-->>Device: PUBACK (原始ACK到达)
          Broker-->>Device: PUBACK (重传ACK到达)
          Note over Broker: 同一消息处理两次
      场景一:ACK延迟导致的"双胞胎消息"
      比喻:
          就像你给朋友发微信问"晚上吃饭吗?",等了3分钟没收到回复,你又发了一遍。结果:
      你朋友其实早就回复了"好的",只是网络延迟刚送到现在你收到了两个"好的"回复
      技术过程:
      1、设备发送温度数据(消息ID=123)
      2、Broker处理成功并返回ACK,但网络抖动导致ACK延迟500ms
      3、设备在300ms没收到ACK,认为丢失就重发消息
      
      最终导致:
      1、Broker收到两条相同的温度数据(ID都是123)
      2、设备收到两个ACK(原始ACK和重发ACK)
      后果:服务端会重复处理同一条数据,可能导致:
      1、数据库插入重复记录
      2、重复执行设备控制命令
1.2 QoS 2的极端情况(概率太小
  • 在PUBREL/PUBCOMP阶段发生网络中断时:

    时序:sequenceDiagram
      Device->>Broker: PUBLISH (QoS 2)
      Broker->>Device: PUBREC
      Note right of Device: 网络中断
      Device->>Broker: PUBLISH (重发)
      Broker-->>Device: PUBREC (重复)


2. 网络问题导致的重复(导致原因之一)

2.1 网络分区(Network Partition)
  • 设备与Broker间短暂断开(<1秒)时:

    • 设备认为发送失败触发重试

    • Broker可能已收到消息但ACK丢失

2.2 TCP重传
  • 当RTO(重传超时)小于MQTT超时时:

    # Linux系统TCP参数(影响重传)
    /proc/sys/net/ipv4/tcp_retries2 = 15  # 默认重试次数
    /proc/sys/net/ipv4/tcp_keepalive_time = 7200

3. 设备端行为产生的重复

3.1 硬件看门狗复位
  • 设备在发送消息后立即复位:

    • 未收到Broker确认

    • 重启后重新发送缓存消息

3.2 应用层重试逻辑
// 典型设备端重试代码(Arduino)
void sendMessage() {
  for (int i = 0; i < 3; i++) {  // 固定次数重试
    if (mqtt.publish(topic, payload)) break;
    delay(1000 * (i + 1));  // 指数退避
  }
}

4. 服务端处理导致的重复

4.1 集群消息广播
  • 在Kafka等消息队列中:

    @KafkaListener(topics = "mqtt-messages")
    public void handle(String message) {
        // 可能被不同消费者实例处理
    }
    • 需配合group.id和分区策略避免重复

4.2 处理超时重试
  • 服务端处理耗时超过MQTT keepalive时:

    # 设备连接参数
    mqtt.keepalive=60  # 60秒心跳

5. 业务场景强化的重复

5.1 设备状态同步
  • 设备断网恢复后集中上报:

    {"type":"batch", "messages":[...]}  // 可能包含历史数据
5.2 定时任务补偿
  • 服务端补发机制:

    @Scheduled(fixedRate = 300000)  // 5分钟补偿
    void checkMissingData() {
        // 可能造成重复补发
    }

6. 其他技术因素

6.1 Broker实现差异
Broker类型重复风险等级典型场景
EMQX集群切换
Mosquitto内存溢出
HiveMQ插件重试
6.2 消息持久化延迟
  • 当Broker使用异步刷盘时:

    # EMQX配置
    persistence.mode = async  # 默认异步模式

技术解决方案对比

方案适用场景性能影响实现复杂度
消息ID去重所有消息
时序编号校验有序消息流
业务幂等设计关键操作
分布式锁集群环境

最佳实践建议

  1. 分层防御

    • 网络层:配置合理的TCP参数

      • 协议层:根据业务选择QoS级别

      • 应用层:实现至少两级的去重(内存+持久化)

    sysctl -w net.ipv4.tcp_syn_retries=3
  2. 监控指标

    # Grafana监控关键指标
    mqtt_duplicate_messages_total{type="qos1"}
    mqtt_network_retransmits_total
  3. 设备SDK规范

    # 设备端应实现的逻辑
    def generate_message_id():
        return f"{device_id}-{time()}-{random(1000)}"

通过理解这些根本原因,可以针对性设计去重策略,在10万台设备规模下将重复消息率控制在0.1%以下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

双木林。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值