在使用Spring amqp创建消费者并接收消息时,通常会用到下面两个接口。
public interface MessageListener {
void onMessage(Message message);
}
public interface ChannelAwareMessageListener {
void onMessage(Message message, Channel channel) throws Exception;
}
我们会实现接口,并通过onMessage方法来接收消息。在接收消息后,处理业务时如果出现异常,那么消费者会不断接收到重发的消息。有时候在出现某些异常,无法处理,因此并不希望继续接收到重发,因此需要用到手工确认模式,来按需进行重发。
1.默认情况下为什么会自动重发?
在配置消费端时,通常使用下面的配置。而对于rabbit:listener-container标签并未指定“确认属性” acknowledge。默认情况下该属性为auto。
<rabbit:listener-container
connection-factory="connectionFactory" >
<rabbit:listener ref="consumer" method="listen" queue-names="myQueue" />
</rabbit:listener-container>
当onMessage方法产生异常后,框架会调用下面的方法处理异常。
org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.rollbackOnExceptionIfNecessary(Throwable)
public void rollbackOnExceptionIfNecessary(Throwable ex) throws Exception {
boolean ackRequired = !this.acknowledgeMode.isAutoAck() && !this.acknowledgeMode.isManual();
try {
if (this.transactional) {
if (logger.isDebugEnabled()) {
logger.debug("Initiating transaction rollback on application exception: " + ex);
}
RabbitUtils.rollbackIfNecessary(this.channel);
}
if (ackRequired) {
boolean shouldRequeue = this.defaultRequeuRejected ||
ex instanceof MessageRejectedWhileStoppingException;
Throwable t = ex;
while (shouldRequeue && t != null) {
if (t instanceof AmqpRejectAndDontRequeueException) {
shouldRequeue = false;
}
t = t.getCause();
}
if (logger.isDebugEnabled()) {
logger.debug("Rejecting messages (requeue=" + shouldRequeue + ")");
}
for (Long deliveryTag : this.deliveryTags) {
this.channel.basicReject(deliveryTag, shouldRequeue);
}
if (this.transactional) {
RabbitUtils.commitIfNecessary(this.channel);
}
}
}
catch (Exception e) {
logger.error("Application exception overridden by rollback exception", ex);
throw e;
}
finally {
this.deliveryTags.clear();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
isAutoAck方法
public boolean isAutoAck() {
return this == NONE;
}
可以看到,如果acknowledge即非manul,也非none时(调用处方法名字是isAutoAck,但内部确实判断是否为NONE),那么会调用this.channel.basicReject。因此发送否定确认,最终不断收到重复发送的消息。
关于否认可以参考下面的链接。
http://www.rabbitmq.com/nack.html
2.对消息进行手工确认
为了在Spring-amqp框架中进行手工确认,在接收消息时需要实现如下的接口。
public interface ChannelAwareMessageListener {
void onMessage(Message message, Channel channel) throws Exception;
}
此外消费者的确认模式需要配置为manual,其中确认模式包括NONE,MANUL,与AUTO三种[1]。
<rabbit:listener-container
connection-factory="connectionFactory" acknowledge="manual">
那么当收到消息后,如果要否认,或确认则通过调用channel对象的下面的两个方法即可。其中basicAck进行确认,而basicNack进行否认。
long deliveryTag = message.getMessageProperties().getDeliveryTag();
throw new IllegalArgumentException("Illegal");
channel.basicAck(deliveryTag, false);
channel.basicNack(deliveryTag, false, true);
上面代码中deliveryTag即消息消交付的一个标识,其作用域为channel。而basicNAck与basicReject都可以进行否则,二者区别参考下面的官网解释。
http://www.rabbitmq.com/nack.html
当在onMessage方法中调用basicAck确认消息后,队列中持久化的消息会被删除。而调用basicNack后,会收到rabbitmq重发的消息。若未调用basicAck确认则消息会产生堆积[2]。那么当消费者下再一次连接rabbitmq时消息会重发给消费者。
[1]Spring-amqp 配置消息的三种确认方式,http://docs.spring.io/spring-amqp/docs/1.6.5.RELEASE/reference/html/_reference.html 3.1.15 Message Listener Container Configuration
[2]消息确认 http://www.rabbitmq.com/tutorials/tutorial-two-java.html Forgotten acknowledgment