MQTT Qos 2中去重可以在PUBREC阶段完成,为什么还一定需要PUBREL和PUBCOMP连个阶段?

我的问题是既然在MQTT中的Qos2中(保证消息能到达并且只到达一次:Exactly-Once Delivery【恰好送达一次】),既然去重可以在PUBREC阶段完成,那为什么还需要PUBREL和PUBCOMP阶段?

请先 登录 后评论

1 个回答

石天

这个问题的简单回答是:

为了实现MQTT 的Qos2 服务质量【Exactly-Once Delivery】,协议的设计者把整个消息的投递过程分了两个阶段和四个步骤。

两个阶段包括:

一,Exactly-Once 阶段,即去重,可以在PUBLISH和PUBREC阶段完成。

1,发送者-->Broker 发送消息,发送者会存储此条消息,以便收不到回复消息(这里是PUBREC)重发此条消息,

2,Broker(接收者)收到消息并存储完成(内存或数据库),回复给发送者PUBREC,发送者若没有接收到RUBREC(网络抖动或处理延迟),

超时后会重发这条消息,Broker(接收者)查看Package_ID发现是重复消息,Broker(接收者)不会存储这条消息,而是简单回复发送者PUBREC消息。

二:Delivery阶段,即交付阶段,需要通过 PUBREL和PUBCOMP阶段完成

核心答案是:PUBREC 阶段的去重是为了解决网络重发导致的重复,但它无法解决接收端在关键时间点(即将交付消息时)失败或重启而导致的消息丢失或重复问题。PUBREL 和 PUBCOMP 阶段是为了在发送端和接收端之间建立一个 “事务边界”,确保双方对消息的状态达成绝对一致,从而应对进程级别的故障。

让我们深入分解这个问题。

1. 只有 PUBLISH 和 PUBREC 的两步协议会有什么问题?

假设我们简化协议,只有两步:

  1. 发送者发送 PUBLISH。

  2. 接收者回复 PUBREC(表示已持久化),然后立即将消息交付给订阅者。

这个模型在大多数情况下似乎也能工作,但它有一个致命的缺陷:状态不一致

考虑以下这个故障场景:

  1. 接收者(Broker)收到了 PUBLISH (ID: 123)。

  2. 接收者成功将消息 123 持久化到磁盘。

  3. 接收者发送了 PUBREC 给发送者。

  4. 就在发送 PUBREC 之后、即将把消息交付给订阅者之前的这一刻,接收者(Broker)突然崩溃了!

  5. 发送者收到了 PUBREC,认为大功告成,于是释放了所有关于消息 123 的状态,并告知上层应用“消息已成功送达”。

  6. 接收者(Broker)重启了。它从磁盘恢复了自己的状态,包括那条已持久化的消息 123。

  7. 问题来了:接收者现在应该如何处理消息 123?

    • 它已经发送过 PUBREC 了,所以它知道发送者那边已经认为这件事结束了。

    • 但它自己不记得是否已经把消息 123 交付给最终的订阅者了。可能交付了,也可能没交付。

  8. 接收者陷入了两难:

    • 如果它不交付消息:如果之前确实没交付,那么消息就丢失了。这违反了“必定送达”的保证。

    • 如果它再次交付消息:如果之前已经交付了,那么订阅者就会收到一条重复的消息。这违反了“不重复”的保证。

这个两阶段协议无法让系统从这种故障中恢复,因为它缺少一个明确的“事务完成”的最终状态。

2. PUBREL 和 PUBCOMP 如何解决这个问题?

QoS 2 的四步协议通过引入一个明确的 “交付阶段” 来解决这个状态一致性问题。它将“存储消息”和“使用消息”这两个动作分离开,并为“使用消息”这个动作也增加了确认机制。

让我们再分析一下刚才的故障场景,但这次是在完整的四步协议中:

  1. 接收者收到了 PUBLISH (ID: 123)。

  2. 接收者持久化消息 123,并将其状态标记为 “已收到,等待释放”

  3. 接收者发送 PUBREC。

  4. 接收者在收到 PUBREL 之前崩溃了。

  5. 发送者收到了 PUBREC,发送 PUBREL,但收不到 PUBCOMP(因为接收者崩溃了)。发送者会持续重试 PUBREL。

  6. 接收者重启,从磁盘恢复状态。它看到了消息 123 的状态是 “已收到,等待释放”

  7. 这时,接收者明确地知道

    • 这个消息还没有被授权交付给订阅者(因为交付的授权就是 PUBREL)。

    • 发送者还在等待 PUBCOMP,所以一定会重发 PUBREL。

  8. 接收者等待发送者重传的 PUBREL。

  9. 收到 PUBREL 后,接收者开始处理:

    • 将消息 123 交付给所有订阅者。

    • 将消息 123 的状态更新为 “已完成”

    • 发送 PUBCOMP 给发送者。

  10. 至此,双方状态达成一致,都可以安全地清理消息 123 的相关资源。

关键区别与总结

PUBREC承诺与去重1. 对发送者的承诺:“消息我已存好,你别再重发 PUBLISH 了。”
2. 网络层去重:防止因网络重发导致的重复存储。
PUBREL交付授权划分事务阶段:这是一个明确的指令:“我现在授权你交付这条消息”。它将接收者的状态从“已存储”推进到“待交付”。
PUBCOMP事务完成最终状态同步:“你授权我交付的消息,我已全部处理完毕。” 这是双方确认整个事务成功的最终信号。

PUBREL 和 PUBCOMP 存在的根本原因是:

它们共同定义了一个原子事务的边界

  1. PUBREC 标志着“事务第一阶段”的结束:消息已经安全地、不重复地被接收方记录在案

  2. PUBREL 标志着“事务第二阶段”的开始:接收方现在可以使用这条消息了(比如投递给订阅者)。

  3. PUBCOMP 标志着整个事务的结束:消息已经被安全地、不重复地使用掉了

如果没有这两个阶段,接收方就无法区分“消息只是被存储了”和“消息已经被使用过了”这两种状态。而在分布式系统中,这种状态的不确定性是导致数据丢失或重复的根本原因。

简而言之:PUBREC 解决了网络层面的重发问题,而 PUBREL/PUBCOMP 解决了系统层面的状态一致性问题,确保即使在接收端发生故障时,双方对消息的最终状态也能达成一致。这才是 QoS 2 实现“恰好一次”语义的完整保障。

        

请先 登录 后评论
  • 1 关注
  • 0 收藏,40 浏览
  • shitian 提出于 2025-09-19 11:35

相似问题