# 飞行窗口和消息队列

## 简介

为了提高消息吞吐效率和减少网络波动带来的影响，EMQX 允许多个未确认的 QoS 1 和 QoS 2 消息同时存在于网络链路上。这些已发送但未确认的消息将被存放在飞行窗口（Inflight Window）中直至完成确认。

如果传输中的 QoS 1 和 QoS 2 消息数量达到飞行窗口最大限制（见 `max_inflight`），新到达的消息不会被立即转发，而是暂存在消息队列（Message Queue）中。

恢复投递时，如果禁用了主题优先级，EMQX 会按先入先出顺序将消息出队。如果启用了主题优先级，EMQX 会根据消息的主题优先级进行调度，并在每个优先级内保持先入先出顺序。QoS 1 和 QoS 2 消息投递后会进入飞行窗口，QoS 0 消息投递后不会进入飞行窗口。

QoS 0 消息通常会绕过飞行窗口并立即转发。对于内存会话，如果连接的发送队列发生拥塞，EMQX 会将后续 QoS 0 消息存入消息队列。如果飞行窗口已满且启用了 `mqueue_store_qos0`，EMQX 也会将 QoS 0 消息入队，使其遵循常规的队列调度，而不会绕过消息队列。连接恢复或飞行窗口产生可用空间后，EMQX 会继续投递队列中的消息。

当某个主题优先级的消息队列达到长度限制时，EMQX 会优先淘汰该优先级中最早入队的 QoS 0 消息。如果该优先级中没有 QoS 0 消息，EMQX 会淘汰该优先级中最早入队的其他消息。禁用主题优先级时，所有消息使用相同的优先级。该策略有助于在 QoS 0 消息突发时保障 QoS 1 和 QoS 2 消息的投递。因此，应设置合适的消息队列最大长度，详情参见 `max_mqueue_len`。

订阅端离线但会话仍保留时，消息队列还会存储此期间到达的消息，并在订阅端重新连接后投递。将 `mqueue_store_qos0` 设置为 `false` 可禁止存储离线期间到达的 QoS 0 消息。

注意，飞行窗口和消息队列不是全局的，EMQX 会为每个客户端连接单独分配飞行窗口和消息队列。

## 飞行窗口与 Receive Maximum

MQTT v5.0 协议为 CONNECT 报文新增了一个 `Receive Maximum` 的属性，官方对它的解释是：

> 客户端使用此值限制客户端愿意同时处理的 QoS 为 1 和 QoS 为 2 的发布消息最大数量。没有机制可以限制服务端试图发送的 QoS 为 0 的发布消息 。

也就是说，服务端可以在等待确认时使用不同的报文标识符向客户端发送后续的 PUBLISH 报文，直到未被确认的报文数量到达 `Receive Maximum` 限制。

不难看出，`Receive Maximum` 其实与 EMQX 中的飞行窗口机制如出一辙，只是在 MQTT v5.0 协议发布前，EMQX 就已经对接入的 MQTT 客户端提供了这一功能。现在，使用 MQTT v5.0 协议的客户端将按照 `Receive Maximum` 的规范来设置飞行窗口的最大长度，而更低版本 MQTT 协议的客户端则依然按照配置来设置。

然而，EMQX 并不一定会授予 CONNECT 数据包中请求的 `Receive Maximum` 值。相反，在 CONNACK 数据包中授予的 `Receive Maximum` 受 `mqtt.max_inflight` 配置的限制。

## 配置项

| 配置项                 | 类型 | 可选值          | 默认值 | 描述                                                   |
| ---------------------- | ---- | --------------- | ------ | ------------------------------------------------------ |
| mqtt.max_inflight      | 整数 | (0, 65536)      | 32     | Inflight 窗口长度限制，0 表示无限制                    |
| mqtt.max_mqueue_len    | 整数 | [0, ∞)          | 1000   | 消息队列长度限制。`0` 表示无限制。                         |
| mqtt.mqueue_store_qos0 | 枚举 | `true`, `false` | true   | 客户端离线、内存会话连接的发送队列拥塞或飞行窗口已满时，EMQX 是否将 QoS 0 消息存入消息队列。 |
