消息重传
消息重传 (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 消息
基础配置
EMQX 会在以下场景中重传未确认的 QoS 1 和 QoS 2 消息:
- 客户端重新连接并恢复会话时,EMQX 会重放未确认的消息,以继续 QoS 投递流程。
- 客户端保持连接期间,如果将
mqtt.retry_interval设置为大于0的时长,EMQX 会周期性重传未确认的消息。
在配置文件中设置重传间隔:
| 配置项 | 类型 | 可取值 | 默认值 | 说明 |
|---|---|---|---|---|
retry_interval | duration | 时长或 infinity | infinity | 客户端保持连接期间,EMQX 重传未确认 QoS 1 和 QoS 2 消息的间隔。设置为 0 或 infinity 可禁用周期重传。会话恢复时,EMQX 仍会重放未确认的消息。 |
一般来说,你只需要关心以上内容就足够了。
如需了解更多 EMQX 在处理 MQTT 协议的重传的细节见以下内容。
协议规范与设计
重传的对象
首先,在了解 EMQX 对于重传机制的设计前,我们需要先确保你已经了解协议中 QoS 1 和 QoS 2 的传输过程,否则请参见 MQTT v3.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 (*)
PUBCOMP
#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 依次发送
12,并将其放入 飞行窗口 中;客户端仅应答消息1;且此时由于客户端发送流出现了问题,无法发送后续应答报文。 - Broker 收到消息
1的应答;从飞行窗口移除消息1;并将3发送出去;继续等待23的应答; - Broker 等待应答超时,重发了报文
23;客户端收到重发的报文23并正常应答。 - Broker 从飞行窗口移除了消息
23,并发送报文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 | boolean | true, false | true | 是否将 QoS 0 消息存入消息队列。 |
max_mqueue_len | integer | 0 - infinity | 1000 | 消息队列的最大长度。 |
max_inflight | integer | 1 - 65535 | 32 | EMQX 可同时投递且尚未收到确认的 QoS 1 和 QoS 2 消息的最大数量。 |
max_awaiting_rel | integer | 0 - infinity | 100 | 每个会话中等待 PUBREL 的 QoS 2 消息的最大数量。 |
await_rel_timeout | duration | > 0 | 300s | 等待 PUBREL 的最长时间。超时后,EMQX 会释放包 ID 并记录警告日志。EMQX 在开始等待 PUBREL 前已投递该 QoS 2 消息。 |