
# RabbitMQ性能优化
**保持尽可能短的队列长度**
太多的消息堆积在队列中会造成内存负载过高，为了释放内存，RabbitMQ 会把消息转存到磁盘，转存过程会耗费大量时间，造成消息处理速度下降或直接阻塞生产流程。因此队列中堆积过多的消息容易对 broker 产生负面效应。除此之外，如果节点崩溃后重启，过多的数据会使得重建索引需要消耗大量时间，集群模式下的节点间同步数据也会非常耗时。
**使用惰性队列提升稳定性**
惰性队列（lazy queues）是 RabbitMQ 3.6 之后新增的特性。惰性队列的消息会自动存储到磁盘，因此减少了内存的使用率，但是会增加I/O开销，影响吞吐量。使用惰性队列能够更好的把控性能，并且使得集群更加的稳定。和非惰性队列不同，消息不会积累在内存中然后等到内存不足再一次性刷到磁盘，造成队列性能不稳定。如果你需要一次发送大量消息，或者消费速度长时间赶不上生产速度，那么我们推荐使用惰性队列。 请注意，以下情况不建议使用惰性队列： a. 追求高性能 b. 队列无明显积压 c. 队列设置了 max-length 策略
**通过TTL或者max-length限制队列长度**
可以通过设置消息 TTL、队列 TTL 和 max-length 来限制队列长度。如果队列长度达到 max-length 值，队列头部的消息会被丢弃或进入死信队列。消息的生存时间到期也会被丢弃或者进入死信队列。
**关注队列个数**
在 RabbitMQ 中，一条队列是由一个线程处理的。利用服务器的多核特性和分布式特性建立多条队列，将不同队列分布到不同 CPU 或不同节点，以此来获取高吞吐量。同时需要注意，过多的队列可能会对 CPU 和内存造成较高的负担，RabbitMQ management 接口的响应速度也会受到影响。
**自动为临时队列分配队列名**
如果使用临时队列（包括排他队列、自动删除队列、非持久化队列），可以调用不带参数的接口queueDeclare()让 RabbitMQ 自动为你分配一个队列名。
**根据需要使用自动删除队列**
如果不再使用的队列资源长期保存在服务端，可能对 RabbitMQ 性能造成影响，可以通过三种方法自动地删除队列：为队列设置 TTL 属性、为队列设置 auto-delete 属性、为队列设置 exclusive 属性。
**控制优先级队列的使用**
每一个优先级会在Erlang VM中使用一个内部队列，这会消耗一定的资源。大多数场景下，使用最多5个优先级就够了。
**如何确定消息大小**
如何选择发往RabbitMQ的消息长度是一个常见问题。记住，每秒钟发送的消息数比消息大小更容易达到瓶颈。虽然发送大消息不是一个好的做法，但是发送多条小的消息也可能不是一个好的选择。更好的方法是生产者把多条小消息封装成一条大消息，然后由消费者来拆开处理。然而，如果一条大消息封装了太多的子消息，处理速度将会受到影响。如果一条子消息处理失败，整个大消息都需要重传。因此，当选择消息大小时，需要考虑带宽和业务架构。
**连接和通道**
每条连接（connection）大约占用 100KB 内存（启动 TLS 则会占用更多）。数以千计的连接会对 RabbitMQ 服务端造成较大的负担，极端情况下会因为 OOM 而崩溃。AMQP 协议可以在单条 TCP 连接上进行多路复用。建议一个进程只创建一条 TCP 连接，每个线程复用这条连接创建各自的通道（channel）**。**连接应该保持长生命周期，因为 AMQP 建立连接需要一定的开销（至少 7 个 TCP 包）。相反，通道则允许较为频繁地开启和关闭，但在发布消息时复用通道也是一个好的习惯，不要每发送一条消息都开启一个新的通道。同时需要注意如下几点：
- 多线程不要共享通道，因为你很难实现线程安全。
- 不要频繁地开启或关闭连接和通道，否则会造成更高的延迟。
- 生产者和消费者使用独立的连接，来提高吞吐量。
- 大量的连接和通道可能会影响管理接口的性能，造成请求超时。
**消息确认**
消费者使用确认（Acknowledgment）机制避免消息因为连接问题而丢失，客户端可以在收到消息或者处理完消息后回给服务端一个 ack 消息。消费者确认机制会对性能造成影响，如果单纯追求高吞吐量，应该关闭手动确认功能。如果消息对于业务来说比较重要，那么应该在消息被处理完之后才确认该条消息。生产者使用 Confirm 机制来实现类似的功能，当服务端收到生产者发来的消息，它会返回一个 ack 消息，以此来实现最少一次（at-least-once）的投递语义。注意，Confirm 机制同样会影响性能。
**控制未确认消息个数**
所有未确认的消息都会暂存在内存中，太多的未确认消息可能造成服务 OOM。为了限制未确认消息的规模，你可以在消费者端开启[prefetch](https://www.rabbitmq.com/confirms.html#channel-qos-prefetch)功能来限制消息拉取上限。
**持久化资源**
为了防止因为服务宕机、重启、硬件问题等原因造成的消息丢失，请使用持久化队列和消息。持久化消息涉及到写磁盘操作，如果使用了惰性队列也会将所有消息写入磁盘，包括非持久化消息。如果单纯追求高性能，可以使用非持久化消息。
**开启TLS连接**
你可以在 AMQP 之上使用 TLS 连接 RabbitMQ。因为数据需要加解密，所以 TLS 对性能有一定的影响。
**如何正确选择 QoS 值**
- 如果只有单个或少量消费者，并且消费速度很快，那么建议 QoS 设置的大一点，使得客户端保持忙碌状态。
- 如果客户端的消息处理速度和带宽保持不变，简单地用公式RTT / 单条消息处理时间就可以估算出应该设置多大的 QoS 值。
- 如果消费者数量较多并且消息处理速度较快，那么建议 QoS 设置的小一点。
- 如果消费者数量较多并且消息处理速度较慢，那么建议 QoS 设置为 1，让消息平均的分发到所有消费者。
请注意，如果客户端使用拉模式或者开启了自动确认（auto-ack），预拉取功能将会失效。一个常见的错误是不限制预拉取消息个数，所有消息全部发送给一个消费者，造成消费者 OOM 崩溃和消息重复投递。更多关于 RabbitMQ 预拉取功能的信息可以参考[这里](https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/)。
**观测性能指标**
|          指标ID          |  指标名称   |            指标说明             |
|---|---|---|
| channels               | 通道数     | 该指标用于统计RabbitMQ实例中的总通道数。    |
| queues                 | 队列数     | 该指标用于统计RabbitMQ实例中的总队列数。    |
| connections            | 连接数     | 该指标用于统计RabbitMQ实例中的总连接数。    |
| connections_usage      | 连接数使用率  | 当前节点实际连接数占最大连接数比率。          |
| rabbitmq_disk_usage    | 磁盘容量使用率 | 统计Rabbitmq节点虚拟机的磁盘容量使用率。    |
| rabbitmq_cpu_usage     | CPU使用率  | 统计Rabbitmq节点虚拟机的CPU使用率。     |
| rabbitmq_memory_usage  | 内存使用率   | 统计Rabbitmq节点虚拟机的内存使用率。      |
| rabbitmq_cpu_core_load | CPU核均负载 | 统计Rabbitmq节点虚拟机CPU每个核的平均负载。 |
   
全量指标可参考[RabbitMQ支持的监控指标](https://support.huaweicloud.com/usermanual-rabbitmq/rabbitmq-ug-180413002.html)。
