SpringAMQP 实践总结-确认机制,prefetch

153 阅读3分钟

最近项目里的rabbitmq发生了消费端内存爆炸,消息堆积,消费者不消费的问题。为了解决这个问题,展开了一系列的调查,现在问题解决,也总结一下最近的调查结果。

SpringAMQP确认模式

为保证可靠性和数据安全性,RabbitMQ提供两种确认机制,发布者确认(Publisher Confirms)和消费者确认(Consumer Delivery Acknowledgements)。这里我们只里聊消费者确认。

RabbitMQ中存在两种消费者确认模式:

  • 自动确认:消息发出后会立即被认为是发送成功。俗称即发即忘。这种模式的特点是提供了高吞吐量但是不能保证数据安全,如果消费者的TCP连接在成功传递之前关闭,那消息就丢了。

  • 手动确认:消息发出后需要消费者手动发出确认。如遇上连接关闭的情况,未被确认的消息将会自动重新入队(requeue)

然鹅,在SpringAMQP里却提供了三种关于确认模式(AcknowledgeMode)的设定:

  • NONE: 对应RabbitMQ里的自动确认
  • MANUAL: 表示spring不会做任何事情,用户需要自己手写确认
  • AUTO: 文档上描述的是 “容器将根据侦听器是否正常返回或抛出异常来发出 ack/nack。”,也就是说对应到RabbitMQ里也是使用手动确认,只是不需要用户手写,相当于框架自己帮你完成了手动确认。

Prefetch count

之前项目里用的是AcknowledgeMode=NONE,也就是自动确认模式。这种方式无视了消费者的消费能力,过量的消息堆积导致了消费端内存爆炸。

为了对应这种情况,RabbitMQ其实提供了一种解决办法,即设置prefetch count

Prefetch数限制了一个channel或一个连接上的未确认消息数量。也就是说设置了Prefetch count之后,如果队列中未确认消息的数量达到了prefetch的上限,那么RabbitMQ就会限制生产者发信,从而避免消费者内存爆炸的问题。

对应到spring 里最简单的实现,可以在applicaiton.yml里进行设定

rabbitmq:
  addresses: ${CLOUDAMQP_URL}
  template:
    mandatory: true
  listener:
    type: simple
    simple:
      acknowledge-mode: auto #确认模式:只有在手动确认模式下,prefetch才能生效
      prefetch: 30 #在此处设置prefetch count

对不同的listener使用不同的确认模式

现在项目默认的设定是acknowledge-mode:none,因为项目里有好多个不同的队列,实现着不同的业务,但是我们只想对其中一个业务繁重的队列使用prefetch设定,也就是需要对其指定acknowledge-mode: auto,同时不想影响其他已有的队列,此时该怎么办嘞?

答案就是创建一个新的ListenerContainer,在listener处指定,来看一下具体做法:

RabbitMQ设定:


@Configuration
public class RabbitmqConfig {
    @Bean
    public SimpleRabbitListenerContainerFactory manualAckContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        // 指定确认模式
        factory.setAcknowledgeMode(AcknowledgeMode.AUTO);
        factory.setDefaultRequeueRejected(false);
        // 指定prefetch count
        factory.setPrefetchCount(30);

        return factory;
    }
}

RabbitListener:

// 指定ListenerContainer为新定义的"manualAckContainerFactory"
@RabbitListener(queues = "queueA", containerFactory = "manualAckContainerFactory")
@RabbitHandler
public void queueAHandler(@Payload List<xxxBean> list, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
    logger.info("queueA处理开始: " + tag);
    // 业务逻辑......
}

这样queueA的消费者就会使用手动确认模式,并应用上prefetch的设定,而其他未做特别指定的队列将使用application.yml里的设定。同理,我们还可以使用此方法为不同队列指定不同的 Retry Setting,messageConverter等等。

By the way, 如果想对不同队列使用不同的meesageConverter ,除了在消费端的listenerContainer处设定之外,还需要在生产者端的rabbitTempalte处也指定,也就是需要新实例化一个rabbitmqTemplate。