请不要每次发送消息都创建连接/通道
RabbitMQ 中创建连接是一个耗时/耗资源的操作,每个连接会使用至少 100KB 的内存,连接数过多会增大 Broker 的内存压力。应该在程序启动时创建连接,每次发送消息时复用此长连接,提升发送性能和减少服务端内存占用。
通道是一种更加轻量的通信方式,建议尽可能多的使用通道来复用连接。但最好不要跨线程并发使用同一个通道,因为很多 RabbitMQ 客户端的通道实现并不是线程安全的。
为生产者设置合理的发送超时时间
RabbitMQ 不同语言和版本的客户端设置了不同的默认发送时间,部分客户端默认超时时间过长,例如 580 秒或 900 秒。网络异常时,过长的发送超时时间会阻塞发送端线程,甚至进一步造成雪崩效应,建议根据业务场景设置合理的超时时间,3 秒是一个推荐的值。
生产者和消费者使用独立的连接
由于 RabbitMQ 有独特的流控机制,如果生产者和消费者复用同一个物理连接,而消费流量大触发了流控,可能会导致生产者被流控导致发送慢或超时,因此建议生产者和消费者初始化时使用不同的物理连接,避免互相影响。
消费者不建议开启自动消息确认
RabbitMQ 服务端提供了至少投递一次的消费语义,以确保消息正确地触达到下游业务系统。一旦消费端开启了自动确认消息,服务端将消息推送给消费方后,就自动确认删除消息,即使消费端处理消息时出现了异常也不会重试,可能会导致业务上漏处理消息。
消费者幂等处理消息
RabbitMQ 服务端提供了至少投递一次的消费语义,极端场景下有可能重复投递消息,因此建议关键业务处理消息时一定要做幂等处理,即使收到重复消息,也不会产生负面业务影响。
业务幂等处理可以通过在消息中加入唯一业务标识,消费端消费时检查此类标识和消息状态等,根据业务需求处理重复消息,保证即使重复接受消息也不会产生业务上的负面影响。
限制队列长度,避免大量堆积消息
过长的队列(大量消息堆积)会占用大量内存,消耗更多服务端系统资源,不仅在运行时需要更长的时间来进行状态同步,并且会导致服务端 Broker 启动恢复时间大大增加。
较短的队列会提供更快的处理速度和系统性能。
因此需要客户端尽可能提高消费能力、队列维度限制 max-length 等手段来保证队列尽可能短。
使用 Consume 还是 Get 消费消息?
Get 是一种基于轮询的拉模式消费方式,每消费一条消息都需要向 Broker 发送一个请求,如果队列中没有消息,可能会导致大量无效空拉导致资源占用。而 Consume 一次可以接收一批消息,并且由服务端根据实际情况推送消息,在绝大多数情况下都应该使用 Consume 而不是 Get 来消费消息。
如果业务处理必须使用 Get 消费消息,应关注业务层面的 Get 机制,避免持续 Get Empty 空拉(队列中已经无待消费的消息,但消费端一直持续 Get)导致的服务端 CPU 负载异常高。
消费者设置合理的 Prefetch Count
Prefetch 设置是消费端为了提升消费吞吐,预先将消息推送到消费端缓存,降低消费等待和延迟的机制。但如果 Prefetch Count 如果过大或不限制,会导致大量消息缓存在消费端,并且服务端 Broker 也在内存维护 unack 消息的状态,占用大量资源;如果消息持续 unack 状态中,这些消息也无法被其他空闲的消费者消费,表现为消费延迟增加或消费者负载不均衡。
建议根据业务消费速率,将 Prefetch Count 设置在合理范围。
消费者设置合理的异常处理策略
消费端消费消息时,遇到了无法处理的异常,未设置自动确认的消息会触发消息重试。如果异常持续无法正常退出,会触发消息被无限重试,不仅会对 Broker 端造成较高的负载压力,也会导致后续的消息无法被合理消费。
客户端重连机制确认
服务端 Broker 在例如 OOM、容器母机故障等极端场景下会自愈重启,日常业务自行操作集群升配等场景也会触发 Broker 重启,为避免 Broker 重启期间客户端持续连接异常,请确保客户端实现了自动重连机制。
客户端 SDK 请不要关闭 heartbeat 设置
heartbeat 在服务端和客户端分别有一个配置值(服务端为 60 秒),最终生效的 heartbeat 由服务端和客户端协商决定,且不同语言/版本的客户端协商机制不同。客户端设置 heartbeat=0 即关闭心跳检测,会导致服务端无法自动剔除长期无数据交互的无用连接,最终可能引起非预期的连接泄露。