这个问题的简单回答是:
为了实现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 的两步协议会有什么问题?
假设我们简化协议,只有两步:
发送者发送 PUBLISH。
接收者回复 PUBREC(表示已持久化),然后立即将消息交付给订阅者。
这个模型在大多数情况下似乎也能工作,但它有一个致命的缺陷:状态不一致。
考虑以下这个故障场景:
接收者(Broker)收到了 PUBLISH (ID: 123)。
接收者成功将消息 123 持久化到磁盘。
接收者发送了 PUBREC 给发送者。
就在发送 PUBREC 之后、即将把消息交付给订阅者之前的这一刻,接收者(Broker)突然崩溃了!
发送者收到了 PUBREC,认为大功告成,于是释放了所有关于消息 123 的状态,并告知上层应用“消息已成功送达”。
接收者(Broker)重启了。它从磁盘恢复了自己的状态,包括那条已持久化的消息 123。
问题来了:接收者现在应该如何处理消息 123?
它已经发送过 PUBREC 了,所以它知道发送者那边已经认为这件事结束了。
但它自己不记得是否已经把消息 123 交付给最终的订阅者了。可能交付了,也可能没交付。
接收者陷入了两难:
如果它不交付消息:如果之前确实没交付,那么消息就丢失了。这违反了“必定送达”的保证。
如果它再次交付消息:如果之前已经交付了,那么订阅者就会收到一条重复的消息。这违反了“不重复”的保证。
这个两阶段协议无法让系统从这种故障中恢复,因为它缺少一个明确的“事务完成”的最终状态。
2. PUBREL 和 PUBCOMP 如何解决这个问题?
QoS 2 的四步协议通过引入一个明确的 “交付阶段” 来解决这个状态一致性问题。它将“存储消息”和“使用消息”这两个动作分离开,并为“使用消息”这个动作也增加了确认机制。
让我们再分析一下刚才的故障场景,但这次是在完整的四步协议中:
接收者收到了 PUBLISH (ID: 123)。
接收者持久化消息 123,并将其状态标记为 “已收到,等待释放”。
接收者发送 PUBREC。
接收者在收到 PUBREL 之前崩溃了。
发送者收到了 PUBREC,发送 PUBREL,但收不到 PUBCOMP(因为接收者崩溃了)。发送者会持续重试 PUBREL。
接收者重启,从磁盘恢复状态。它看到了消息 123 的状态是 “已收到,等待释放”。
这时,接收者明确地知道:
这个消息还没有被授权交付给订阅者(因为交付的授权就是 PUBREL)。
发送者还在等待 PUBCOMP,所以一定会重发 PUBREL。
接收者等待发送者重传的 PUBREL。
收到 PUBREL 后,接收者开始处理:
将消息 123 交付给所有订阅者。
将消息 123 的状态更新为 “已完成”。
发送 PUBCOMP 给发送者。
至此,双方状态达成一致,都可以安全地清理消息 123 的相关资源。
关键区别与总结
| PUBREC | 承诺与去重 | 1. 对发送者的承诺:“消息我已存好,你别再重发 PUBLISH 了。” 2. 网络层去重:防止因网络重发导致的重复存储。 |
| PUBREL | 交付授权 | 划分事务阶段:这是一个明确的指令:“我现在授权你交付这条消息”。它将接收者的状态从“已存储”推进到“待交付”。 |
| PUBCOMP | 事务完成 | 最终状态同步:“你授权我交付的消息,我已全部处理完毕。” 这是双方确认整个事务成功的最终信号。 |
PUBREL 和 PUBCOMP 存在的根本原因是:
它们共同定义了一个原子事务的边界。
PUBREC 标志着“事务第一阶段”的结束:消息已经安全地、不重复地被接收方记录在案。
PUBREL 标志着“事务第二阶段”的开始:接收方现在可以使用这条消息了(比如投递给订阅者)。
PUBCOMP 标志着整个事务的结束:消息已经被安全地、不重复地使用掉了。
如果没有这两个阶段,接收方就无法区分“消息只是被存储了”和“消息已经被使用过了”这两种状态。而在分布式系统中,这种状态的不确定性是导致数据丢失或重复的根本原因。
简而言之:PUBREC 解决了网络层面的重发问题,而 PUBREL/PUBCOMP 解决了系统层面的状态一致性问题,确保即使在接收端发生故障时,双方对消息的最终状态也能达成一致。这才是 QoS 2 实现“恰好一次”语义的完整保障。