RabbitMQ永远带有Spring重新传递信息

时间:2015-01-19 15:06:11

标签: spring rabbitmq amqp

我正在使用Spring和RabbitMQ,我试图避免在发生运行时异常时重新发送消息。我试图在requeue-reject中将false设置为listener-container并配置一个引发AmqpRejectAndDontRequeueException的自定义错误处理程序。似乎两种策略都失败了,而且这些消息也会永远地重新传递。有什么想法吗?

感谢您的帮助。

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.1.xsd">

    <rabbit:connection-factory id="rabbitMQConnectionFactory" host="localhost" port="5672" username="guest" password="guest" />

    <rabbit:admin connection-factory="rabbitMQConnectionFactory" />

    <rabbit:template id="amqpTemplate" connection-factory="rabbitMQConnectionFactory" />

    <rabbit:queue name="q1" />
    <rabbit:queue name="q2" />

    <rabbit:listener-container error-handler="errorHandler" connection-factory="rabbitMQConnectionFactory" concurrency="10" transaction-manager="transactionManager" requeue-rejected="false">
        <rabbit:listener ref="q1Listener" method="consumeMessage" queue-names="q1" />
        <rabbit:listener ref="q2Listener" method="consumeMessage" queue-names="q2" />
    </rabbit:listener-container>

    <bean id="errorHandler" class="ErrorHandler" />

    <bean id="q1Listener" class="Q1MessageConsumerBean" />
    <bean id="q2Listener" class="Q2MessageConsumerBean" />
</beans>

2 个答案:

答案 0 :(得分:3)

首先,requeue-rejected="false"AmqpRejectAndDontRequeueException在目标侦听器RuntimeException的情况下也是如此。

这是一个测试用例,显示requeue-rejected="false"效果很好。

<rabbit:connection-factory id="connectionFactory" host="localhost" />

<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"
    exchange="foo" routing-key="foo" />

<rabbit:admin connection-factory="connectionFactory" />

<rabbit:queue name="foo" />

<rabbit:direct-exchange name="foo">
    <rabbit:bindings>
        <rabbit:binding queue="foo" key="foo" />
    </rabbit:bindings>
</rabbit:direct-exchange>

<rabbit:listener-container connection-factory="connectionFactory" auto-startup="false" requeue-rejected="false">
    <rabbit:listener ref="listener" queue-names="foo" />
</rabbit:listener-container>

<bean id="listener" class="org.mockito.Mockito" factory-method="spy">
    <constructor-arg>
        <bean class="org.springframework.amqp.rabbit.listener.RejectedTests$ThrowListener" />
    </constructor-arg>
</bean>

@Autowired
private RabbitTemplate rabbitTemplate;

@Autowired
private SimpleMessageListenerContainer container;

@Autowired
private ThrowListener throwListener;

@Test
public void test() throws Exception {
    rabbitTemplate.convertAndSend("foo");
    container.start();
    Thread.sleep(2000);
    Mockito.verify(throwListener).onMessage(Mockito.any(Message.class));
}

public static class ThrowListener implements MessageListener {

    @Override
    public void onMessage(Message message) {
        throw new RuntimeException("intentional reject");
    }

}

Mockito.verify(throwListener).onMessage(Mockito.any(Message.class));确认onMessage仅在第一次投放时被调用一次。第二次交付没有发生,因为我们的消息被拒绝并从RabbitMQ Broker中删除。

我只看到符号位置,它不会独立于requeue-rejected="false" - RabbitResourceHolder#rollbackAll()发生:

for (Long deliveryTag : deliveryTags.get(channel)) {
    try {
        channel.basicReject(deliveryTag, true);
    } catch (IOException ex) {
    throw new AmqpIOException(ex);
    }
}

但是,只有TX提交和该提交的Expception才会到达此块。因此,在这种情况下,我们会导致TX回滚:

private boolean doReceiveAndExecute(BlockingQueueConsumer consumer) throws Throwable {

    Channel channel = consumer.getChannel();

    for (int i = 0; i < txSize; i++) {

        logger.trace("Waiting for message from consumer.");
        Message message = consumer.nextMessage(receiveTimeout);
        if (message == null) {
            break;
        }
        try {
            executeListener(channel, message);
        }
        catch (ImmediateAcknowledgeAmqpException e) {
            break;
        }
        catch (Throwable ex) {
            consumer.rollbackOnExceptionIfNecessary(ex);
            throw ex;
        }

    }

    return consumer.commitIfNecessary(isChannelLocallyTransacted(channel));

注意最后一行。 consumer.rollbackOnExceptionIfNecessary(ex);是从侦听器抛出异常的情况。否则,我们到达最后一行并等待外部TX提交。

如果您的情况如此,请告诉我们。

答案 1 :(得分:2)

非常感谢您的回答。我做了一些其他的测试,现在我有相同配置的预期行为(删除了errorHandle)。

FYK,在​​我的一次测试中,我发现了一个重新发送的场景,它可能与你说的内容有关:

流程:Listener - &gt; Facade - &gt; Service。所有交易。

如果Service抛出一个RuntimeException而我陷入了Facade并且没有重新抛出(吞下异常),则会重新传递该消息。看起来tx是回滚的,即使我吞下异常并重新传递消息 - 忽略requeue-rejected属性。

再次感谢你。