# 飞行窗口和消息队列

## 简介

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

如果传输中的 QoS 1，2 消息数量到达了飞行窗口最大限制（见 `max_inflight`），那么新到达的消息将不会被立即转发，而是会被暂存在消息队列（Message Queue）中。只有前面的消息完成确认并从飞行窗口中移除，消息队列中的消息才会以先入先出的顺序被发送，同时被添加到飞行窗口中。QoS 0 消息不受此影响，它永远会被立即转发。

如果消息队列也到达了长度限制，后续的报文将依然缓存到消息队列，但相应地，消息队列中最老的消息将被丢弃。因此，设置一个合适的消息队列最大长度（见 `max_mqueue_len`）是非常重要的。

消息队列还会被用来存储在订阅端离线期间到达的消息（包括 QoS 0 消息），这些消息将在订阅端下次上线时被发送。考虑到 QoS 0 消息可能拥有较低的重要性，你可以选择禁止 EMQX 将 QoS 0 消息存储到队列，见 `mqueue_store_qos0`。

注意，飞行窗口和消息队列不是全局的，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 消息存储到消息队列中 |
