消息重传
消息重传 (Message Retransmission) 是属于 MQTT 协议标准规范的一部分。
协议中规定了作为通信的双方 服务端 和 客户端 对于自己发送到对端的 PUBLISH 消息都应满足其 服务质量 (Quality of Service levels) 的要求。如:
QoS 1:表示 消息至少送达一次 (At least once delivery);即发送端会一直重发该消息,除非收到了对端对该消息的确认。意思是在 MQTT 协议的上层(即业务的应用层)相同的 QoS 1 消息可能会收到多次。
QoS 2:表示 消息只送达一次 (Exactly once delivery);即该消息在上层仅会接收到一次。
虽然,QoS 1 和 QoS 2 的 PUBLISH 报文在 MQTT 协议栈这一层都会发生重传,但请你谨记的是:
- QoS 1 消息发生重传后,在 MQTT 协议栈上层,也会收到这些重发的 PUBLISH 消息。
- QoS 2 消息无论如何重传,最终在 MQTT 协议栈上层,都只会收到一条 PUBLISH 消息
基础配置
有两种场景会导致消息重发:
- PUBLISH 报文发送给对端后,规定时间内未收到应答。则重发这个报文。
- 在保持会话的情况下,客户端重连后;EMQX 会自动重发 未应答的消息,以确保 QoS 流程的正确。
在 etc/emqx.conf
中可配置:
配置项 | 类型 | 可取值 | 默认值 | 说明 |
---|---|---|---|---|
retry_interval | duration | - | 30s | 等待一个超时间隔,如果没收到应答则重传消息 |
一般来说,你只需要关心以上内容就足够了。
如需了解更多 EMQX 在处理 MQTT 协议的重传的细节见以下内容。
协议规范与设计
重传的对象
首先,在了解 EMQX 对于重传机制的设计前,我们需要先确保你已经了解协议中 QoS 1 和 QoS 2 的传输过程,否则请参见 MQTTv3.1.1 - QoS 1: At least once delivery 和 MQTTv3.1.1 - QoS 2: Exactly once delivery。
此处,仅作一个简单的回顾,用来说明不同 QoS 下重传的对象有哪些。
QoS 1
QoS 1 要求消息至少送达一次;所以消息在 MQTT 协议层中,可能会不断的重传,直到发送端收到了该消息的确认报文。
其流程示意图如下:
PUBLISH
#1 Sender ---------------> Receiver (*)
PUBACK
#2 Sender <--------------- Receiver
- 涉及到 2 个报文;共 2 次发送动作,发送端和接收端各 1 次;这 2 个报文都持有相同的 PacketId。
- 行尾标记为 * 号的,表示发送方在等待确认报文超时后,可能会主动发起重传。
可见 QoS 1 消息只需要对 PUBLISH 报文进行重发
QoS 2
QoS 2 要求消息只送达一次;所以在实现它时,需要更复杂的流程。其流程示意图如下:
PUBLISH
#1 Sender ---------------> Receiver (*)
PUBREC
#2 Sender <--------------- Receiver
PUBREL
#3 Sender ---------------> Receiver (*)
PUBCOM
#4 Sender <--------------- Receiver
- 涉及到 4 个报文;共 4 次发送动作,发送端和接收端各 2 次;这 4 个报文都持有相同的 PacketId。
- 行尾标记为 * 号的,表示发送方在等待确认报文超时后,可能会主动发起重传。
可见 QoS 2 消息需要对 PUBLISH 和 PUBREL 报文进行重发
综上:
重传动作 都是由于 发送端 报文发送后,在 规定时间 内未收到其期待的返回而触发的。
重传对象 仅包含以下三种:
- QoS 1 的 PUBLISH 报文
- QoS 2 的 PUBLISH 报文
- QoS 2 的 PUBREL 报文
当 EMQX 作为 PUBLISH 消息的接收端时,它不需要重发操作
飞行窗口与最大接收值
其概念的定义和解释参见 飞行窗口与消息队列
引入这两个概念的作用是为了理解:
- EMQX 作为发送端时,再次重发的消息,必然是已存储在飞行窗口中的消息
- EMQX 作为接收端时,发送端重发的消息时:
- 如 QoS 1,EMQX 则直接回复 PUBACK 进行应答;
- 如 QoS 2,EMQX 则会释放,存储在 最大接收消息 队列中的 PUBLISH 或者 PUBREL 报文。
消息顺序
当然,以上的概念仅需要了解即可,你最需要关心的是,消息在被重复发送后,消息顺序出现的变化,尤其是 QoS 1 类的消息。例如:
假设,当前飞行窗口设置为 2 时,EMQX 计划向客户端的某主题投递 4 条 QoS 1 的消息。并假设客户端程序、或网络在中间出现过问题,那么整个发送流程会变成:
#1 [4,3,2,1 || ] -----> []
#2 [4,3 || 2, 1] -----> [1, 2]
#3 [4 || 3, 2] -----> [1, 2, 3]
#4 [4 || 3, 2] -----> [1, 2, 3, 2, 3]
#5 [ || 4] -----> [1, 2, 3, 2, 3, 4]
#6 [ || ] -----> [1, 2, 3, 2, 3, 4]
流程共 6 个步骤;左边表示 EMQX 的 消息队列 和 飞行窗口,以 ||
分割;右侧表示客户端收到的消息顺序,其中每步表示:
- Broker 将 4 条消息放入消息队列中。
- Broker 依次发送
1
2
,并将其放入 飞行窗口 中;客户端仅应答消息1
;且此时由于客户端发送流出现了问题,无法发送后续应答报文。 - Broker 收到消息
1
的应答;从飞行窗口移除消息1
;并将3
发送出去;继续等待2
3
的应答; - Broker 等待应答超时,重发了报文
2
3
;客户端收到重发的报文2
3
并正常应答。 - Broker 从飞行窗口移除了消息
2
3
,并发送报文4
;客户端收到了报文 4 并回复应答。 - 至此,所有报文处理完成。客户端收到的报文顺序为
[1, 2, 3, 2, 3, 4]
,并也依次上报给 MQTT 协议栈的上层。
虽然,存在重复的报文消息。但这是完全符合协议的规范的,每个报文第一次出现的位置都是有序的,并且重复收到的报文 2
3
的报文中,会携带一个标识位,表明其为重发报文。
MQTT 协议和 EMQX 将这个主题认为是 有序的主题 (Ordered Topic)
参见:MQTTv3.1.1 - Message ordering。
它确保 相同的主题和 QoS 下,消息是按顺序投递和应答的。
此外,如果用户期望所有主题下的 QoS 1 与 QoS 2 消息都严格有序,那么需要设置飞行窗口的最大长度为 1,但代价是会降低该客户端的吞吐。
相关配置
此节列举了上述机制中,用到的所有配置。它们都包含在 etc/emqx.conf
中:
配置项 | 类型 | 可取值 | 默认值 | 说明 |
---|---|---|---|---|
mqueue_store_qos0 | bool | true , false | true | 是否将 QoS 0 消息存入消息队列中 |
max_mqueue_len | integer | >= 0 | 1000 | 消息队列长度 |
max_inflight | integer | >= 0 | 0 | 飞行窗口大小;默认 0 即无限制 |
max_awaiting_rel | integer | >= 0 | 0 | 最大接收;默认 0 即无限制 |
await_rel_timeout | durtaion | > 0 | 300s | 最大接收 中消息等待释放的最大超时时间;超过则直接丢弃 |