RabbitMQ在使用者重新连接前丢失消息

RabbitMQ在使用者重新连接前丢失消息,rabbitmq,amqp,Rabbitmq,Amqp,我实现了一个使用者,如果底层连接关闭,它将在一段时间后自动重新连接到代理。我的情况如下: @Override public void consume(final String exchangeName, final String queueName, final String routingKey, final int qos) throws IOException, InterruptedException { ConnectionFactory factory

我实现了一个使用者,如果底层连接关闭,它将在一段时间后自动重新连接到代理。我的情况如下:

    @Override
public void consume(final String exchangeName, final String queueName, final String routingKey,
        final int qos) throws IOException, InterruptedException {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost(this.getHost());

    while (true) {
        Connection connection = null;
        try {
            connection = factory.newConnection();
            Channel channel = connection.createChannel();

            channel.exchangeDeclare(exchangeName, "topic");
            // declare a durable, non-exclusive, non-autodelete queue.
            channel.queueDeclare(queueName, true, false, false, null);
            channel.queueBind(queueName, exchangeName, routingKey);
            // distribute workload among all consumers, consumer will
            // pre-fetch
            // {qos}
            // messages to local buffer.
            channel.basicQos(qos);

            logger.debug(" [*] Waiting for messages. To exit press CTRL+C");

            QueueingConsumer consumer = new QueueingConsumer(channel);
            // disable auto-ack. If enable auto-ack, RabbitMQ delivers a
            // message to
            // the customer it immediately removes it from memory.
            boolean autoAck = false;
            channel.basicConsume(queueName, autoAck, consumer);

            while (true) {
                QueueingConsumer.Delivery delivery = consumer.nextDelivery();
                try {
                    RabbitMessageConsumer.this.consumeMessage(delivery);
                }
                catch (Exception e) {
                    // the exception shouldn't affect the next message
                    logger.info("[IGNORE]" + e.getMessage());
                }
                channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
            }
        }
        catch (Exception e) {
            logger.warn(e);
        }

        if (autoReconnect) {
            this.releaseConn(connection);
            logger.info("[*] Will try to reconnect to remote host(" + this.getHost() + ") in "
                    + this.reconnectInterval / 1000 + " seconds.");
            Thread.sleep(this.getReconnectInterval());
        }
        else
            break;
    }
}

private void releaseConn(Connection conn) {
    try {
        if (conn != null)
            conn.close();
    }
    catch (Exception e) {
        // simply ignore this exception
    }
}
  • 成功启动RabbitMQ服务器
  • 成功启动消费者
  • 已发布消息,消费者已成功接收该消息
  • 停止RabbitMQ服务器,使用者将显示异常:

    com.rabbitmq.client.ShutdownSignalException:连接错误;原因:{#方法(回复代码=541,回复文本=INTERNAL_ERROR,类id=0,方法id=0),null,“}

    然后消费者将在重新连接前休眠60秒

  • 再次启动RabbitMQ服务器
  • 成功发布消息,命令“列表队列”的结果为0
  • 60秒后,使用者再次连接到RabbitMQ,但现在收到在步骤6发布的消息
  • 发布第三条消息,消费者已成功接收
  • 在这种情况下,重新连接之前发布的所有消息都将丢失。 我还做了另一个实验

  • 启动RabbitMQ,并成功发布消息(未启动使用者进程)
  • 停止RabbitMQ,然后重新启动它
  • 启动消费者流程,成功接收在步骤#1发布的消息
  • 注:用户的QOS为1。 我已经研究RabbitMQ好几天了,据我所知,消费者应该在重新连接之前获得发布的消息。 请提供帮助(我运行了基于windows rabbitMQ的测试)

    以下是出版商:

    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost(this.getHost());
    connection = factory.newConnection();
    Channel channel = connection.createChannel();               
    channel = conn.createChannel();
    // declare a 'topic' type of exchange
    channel.exchangeDeclare(exchangeName, "topic");
    // Content-type "application/octet-stream", deliveryMode 2
    // (persistent), priority zero
    channel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_BASIC, message);
    connection.close();
    
    消费者信息如下:

        @Override
    public void consume(final String exchangeName, final String queueName, final String routingKey,
            final int qos) throws IOException, InterruptedException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(this.getHost());
    
        while (true) {
            Connection connection = null;
            try {
                connection = factory.newConnection();
                Channel channel = connection.createChannel();
    
                channel.exchangeDeclare(exchangeName, "topic");
                // declare a durable, non-exclusive, non-autodelete queue.
                channel.queueDeclare(queueName, true, false, false, null);
                channel.queueBind(queueName, exchangeName, routingKey);
                // distribute workload among all consumers, consumer will
                // pre-fetch
                // {qos}
                // messages to local buffer.
                channel.basicQos(qos);
    
                logger.debug(" [*] Waiting for messages. To exit press CTRL+C");
    
                QueueingConsumer consumer = new QueueingConsumer(channel);
                // disable auto-ack. If enable auto-ack, RabbitMQ delivers a
                // message to
                // the customer it immediately removes it from memory.
                boolean autoAck = false;
                channel.basicConsume(queueName, autoAck, consumer);
    
                while (true) {
                    QueueingConsumer.Delivery delivery = consumer.nextDelivery();
                    try {
                        RabbitMessageConsumer.this.consumeMessage(delivery);
                    }
                    catch (Exception e) {
                        // the exception shouldn't affect the next message
                        logger.info("[IGNORE]" + e.getMessage());
                    }
                    channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                }
            }
            catch (Exception e) {
                logger.warn(e);
            }
    
            if (autoReconnect) {
                this.releaseConn(connection);
                logger.info("[*] Will try to reconnect to remote host(" + this.getHost() + ") in "
                        + this.reconnectInterval / 1000 + " seconds.");
                Thread.sleep(this.getReconnectInterval());
            }
            else
                break;
        }
    }
    
    private void releaseConn(Connection conn) {
        try {
            if (conn != null)
                conn.close();
        }
        catch (Exception e) {
            // simply ignore this exception
        }
    }
    

    由于它是一个“主题”交换,所以在发布服务器上没有声明队列。但是,在第1个测试的步骤#3,已声明持久队列,并且消息也是持久的。我不明白为什么重新连接之前会丢失消息。

    如果发布时没有队列,消息将丢失。您是否连接到同一队列(如果是),它是否持久,还是在重新启动RMQ服务器后发布消息之前重新创建它?听起来解决方案是:

  • 使队列持久并在重新启动后重新连接到该队列
  • 确保在发布之前已创建队列

  • 此外,请确保您正在重新连接到消费者中的正确队列。1) 可能是两种解决方案中更好的一种。

    哦,我找到了原因……消息和队列当然是持久的,但是交换不是持久的。由于交换不持久,队列和交换之间的绑定信息将在RabbitMQ代理重新启动之间丢失


    现在,我将exchange声明为持久的,使用者可以在使用者重新启动之前和代理重新启动之后获得发布的消息。

    在我从3.1升级到3.3之后,我的RabbitMQ集群会发生这种情况。我的解决方案是删除
    /var/lib/rabbitmq/mnesia
    目录

    而不是
    channel.queueDeclare(队列名称,true,false,false,null)

    使用这个
    channel.queueDeclare(队列名称,false,false,null)


    重新启动RabbitMQ后,队列是否存在?它是否绑定到要将邮件发布到的exchange?是的,队列是持久的。在第二次测试中,在重新启动rabbitMQ代理后,使用者成功接收到消息。在重新启动rabbitMQ服务器之后和使用者重新连接之前,“list_queues”命令的结果是“Listing queues…com.mpos.lotket.te.thirdpartyservice.amqp.RabbitMessagePublisher 0…done”。这是使用者声明的队列。