RabbitMQ QueueingConsumer可能存在内存泄漏

时间:2012-10-02 09:02:13

标签: java memory-leaks jms rabbitmq

我有以下代码来声明队列:

Connection connection = RabbitConnection.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(getQueueName(), false, false, false, null);
consumer = new QueueingConsumer(channel);
channel.basicConsume(getQueueName(), true,consumer);

和以下内容获取下一个Delivery对象并进行处理:

    Delivery delivery = null;
    T queue = null;

    //loop over, continuously retrieving messages
    while(true) {

        try {
            delivery = consumer.nextDelivery();
            queue = deserialise(delivery.getBody());

            process(queue);

        } catch (ShutdownSignalException e) {
            logger.warn("Shutodwon signal received.");
            break;
        } catch (ConsumerCancelledException e) {
            logger.warn("Consumer cancelled exception: {}",e.getMessage());
            break;
        } catch (InterruptedException e) {
            logger.warn("Interuption exception: {}", e);
            break;
        }
    }

反序列化代码。如你所见,我正在使用Kryo:

public T deserialise(byte[] body) {
    Kryo kryo= new Kryo();
    Input input = new Input(body);
    T deserialised = kryo.readObject(input, getQueueClass());
    input.close();

    return deserialised;
}

如果我使用包含大量对象的队列运行它,在大约270万个对象之后,我会遇到内存不足异常。我最初通过运行它来发现这一点,数据从JMeter以~90 / s的速度进入,起初它没有任何麻烦,但是早上我注意到RabbitMQ中有大量的数据并且内存不足异常消费者。我再次运行它并使用Eclipse Memory Analyzer来确定这个内存的使用位置。从中可以看出,com.rabbitmq.client.QueueingConsumer引用的java.util.concurrent.LinkedBlockingQueue正在增长并且不断增长,直到内存不足为止。

我是否需要做任何事情来告诉Rabbit释放资源?

我可以增加堆大小,但我担心这只是一个短期修复,我的代码中可能会有一些内容可能会让我在生产部署几个月后出现内存泄漏。

4 个答案:

答案 0 :(得分:6)

我的错误是我将我的频道设置为自动确认。这意味着来自Rabbit的每条消息都得到了确认(被收到的信息)。我通过将通道设置为不自动确认(channel.basicConsume(getQueueName(), false,consumer);来修复(并测试)了这一点,然后在我处理队列之后,我收到消息:consumer.getChannel().basicAck(delivery.getEnvelope().getDeliveryTag(), false);

这就是我的队列解除现在的样子:

        Connection connection = RabbitConnection.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(getQueueName(), false, false, false, null);
        consumer = new QueueingConsumer(channel);
        channel.basicConsume(getQueueName(), false,consumer);

以及以下处理队列:

    Delivery delivery = null;
    T queue = null;

    //loop over, continuously retrieving messages
    while(true) {

        try {
            delivery = consumer.nextDelivery();
            queue = deserialise(delivery.getBody());
            process(queue);
            consumer.getChannel().basicAck(delivery.getEnvelope().getDeliveryTag(), false);

        } catch (ShutdownSignalException e) {
            logger.warn("Shutodwon signal received.");
            break;
        } catch (ConsumerCancelledException e) {
            logger.warn("Consumer cancelled exception: {}",e.getMessage());
            break;
        } catch (InterruptedException e) {
            logger.warn("Interuption exception: {}", e);
            break;
        } catch (IOException e) {
            logger.error("Could not ack message: {}",e);
            break;
        }
    }

我现在可以在RabbitMQ管理屏幕中看到消息以非常高的速率传送,但是它们没有以该速率获取。如果我然后杀死我的消费者,在大约30秒内,所有这些非ack'd消息都会被移回Ready就绪队列。我将做的一项改进是设置basicQos值:channel.basicQos(10);,以便没有太多的消息传递但是没有确认。这是可取的,因为这意味着我可以将另一个消费者启动到同一个队列并开始处理队列,而不是所有队列都以非ack'd结束并且不可供其他消费者使用。

答案 1 :(得分:2)

解决方案是设置basicQos - channel.basicQos(2);。我的频道声明现在看起来像这样:

        Connection connection = RabbitConnection.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(getQueueName(), false, false, false, null);
        consumer = new QueueingConsumer(channel);
        channel.basicConsume(getQueueName(), true,consumer);
        channel.basicQos(2);

将basicQos设置为2表示仅在内部存储器中保留2条消息。有关使用CoDel算法的更多信息和有趣讨论,请参阅http://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/

答案 2 :(得分:1)

问题似乎是您的消费者无法跟上您的生产者,导致您的队列无限制地增长。您需要限制队列的大小,并在达到限制时减慢生产者的速度。我还会考虑优化您的消费者,以便它可以跟上。

答案 3 :(得分:1)

这可能是物品在被消耗后没有被破坏的问题。你能告诉你反序列化的代码吗?我怀疑你是通过队列发送对象并使用某种对象输入流/字节数组输入流反序列化它们。如果你没有正确关闭流可能导致内存泄漏。