目录
消息确认
消息确认机制
和发布确认不同,发布确认是生产者和Broker之间的,由Broker向生产者发布一个确认信息,消息确认是消费者向broker发布一个确认信息。
生产者向broker发送完消息后,可能会有两种情况,一种是消息处理成功,一种是消息处理异常。但是RabbitMQ向消费者发送完消息后,就会把这条消息丢弃,如果消费者那边处理异常了就会造成这条消息的丢失。所以为了保证消息的可靠性RabbitMQ提供了消息确认机制。
确认方式分为手动和自动,消费者订阅队列时,就可以指定autoAck参数来进行选择
- 自动确认:当autoAck等于true时,RabbitMQ会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正地消费到了这些消息。自动确认模式适合对于消息可靠性要求不高的场景。
- 手动确认:当autoAck等于false时,RabbitMQ会等待消费者显式地调用Basic.Ack命令,回复确认信号后才从内存(或者磁盘)中移去消息。这种模式适合对消息可靠性要求比较高的场景。
自动确认,当消费者拿到消息后就会自动向RabbitMQ发送确认消息,就好像是有些聊天软件的已读未读功能,当用户点进去后信息就会立刻显示已读,不管用户是否真的已读,所以这种方式的可靠性要相对差一点。
手动确认时可以将队列中的信息分为两个部分,一时等待发送给消费者的,二是以及发送,正在等待确认的,如果RabbitMQ一直没有收到确认信号,并且消费这条消息的消费者已经断开链接了,那么RabbitMQ就会重新安排这条消息入队列,等待下一个消费者。
手动确认
RabbitMQ提供了三种手动确认方法,并且这些确认方法都是与其对应的Channel(通道)相关,换言之消息确认是在同一个Channel内进行。
- 肯定确认: Channel.basicAck(long deliveryTag,boolean multiple)
当RabbitMQ 收到肯定确认后就会认为该条消息被成功处理了,可以将其丢掉了。
**deliveryTag:**消息的唯一标识,它是一个单调递增的64位长整型值。deliveryTag 是每个通道(Channel)独立维护的,所以在每个通道上都是唯一的,而且不同的通道deliveryTag可以有重复。当消费者确认(ack)一条消息时,必须使用对应的通道进行确认。
**multiple:**是否批量确认。在某些情况下,为了减少网络流量,可以对一系列连续的 deliveryTag 进行批量确认。值为 true 则会一次性 ack 所有小于或等于指定 deliveryTag 的消息。值为 false,则只确认当前指定 deliveryTag 的消息。
批量确认:假如开启批量确认,当前回复的deliveryTag是10的话,那么就会一次性将10之前的没有确认的消息一起回复,如果不开启的话就是只回复当前消息
这里的deliveryTag和multiple,跟发布确认里的一样
- 否定确认: Channel.basicReject(long deliveryTag,boolean requeue)
当RabbitMQ收到否定确认后表示客户端拒绝了这条消息
**deliveryTag:**和刚刚的一样
**requeue:**表示拒绝后,这条消息如何处理。如果 requeue 参数设置为 true,则 RabbitMQ 会重新将这条消息存入队列,以便可以发送给下一个订阅的消费者。如果 requeue 参数设置为 false,则 RabbitMQ 会把消息从队列中移除,而不会把它发送给新的消费者。
- 否定确认: Channel.basicNack(long deliveryTag,boolean multiple,boolean requeue)
刚刚那条命令一次只能拒绝一条消息,而Channel.basicNack可以进行批量拒绝,使用方法就是前两个方法的结合

在RabbitMQ的管理界面里也可以看到,这里的get message其实也可以看作是一个消费者,从队列中拿取数据,自上而下分别是,不确认拿完后放回队列,自动确认拿完后不放会队列,拒绝并重新入队,拒绝但不重新入队
代码实现
以上方法是RabbitMQ本身的方法,而这里使用的是Spring来做实现,Spring提供了三种确认策略,和上面的方法并不是一一对应,而是进行了封装。
AcknowledgeMode.NONE
在这种模式下,消息一旦投递给消费者,不管消费者是否成功处理了消息,RabbitMQ 就会自动确认消息,并从队列中移除。如果消费者处理失败,消息可能丢失。这个可以对应上面的自动确认
AcknowledgeMode.AUTO(默认)
在这种模式下,消费者处理成功时会自动确认消息;如果处理过程中抛出异常,则不会确认消息。这个是介于自动确认和手动确认之间。
AcknowledgeMode.MANUAL
在手动确认模式下,消费者必须在成功处理消息后显式调用 basicAck 方法来确认消息。如果消息未被确认,RabbitMQ 会认为消息尚未成功处理,并在消费者可用时重新投递。该模式提高了可靠性:即使消费者处理失败,消息也不会丢失,而是可以被重新处理。

Constants
java
public class Constants {
public final static String ACK_QUEUE = "ack.queue";
public final static String ACK_EXCHANGE = "ack.exchange";
}
Producer
java
@RestController
@RequestMapping("producer")
public class ProducerController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("create1")
public String create1(){
rabbitTemplate.convertAndSend(Constants.ACK_EXCHANGE,"ack","hello ACK");
return "发送成功";
}
}
Consumer
java
@Component
public class AckListener {
@RabbitListener(queues = Constants.ACK_QUEUE)
public void handMessage(Message message, Channel channel ) throws IOException {
System.out.printf("收到的消息:%s\t deliveryTag: %d\n",new String(message.getBody(),"UTF-8")
,message.getMessageProperties().getDeliveryTag());
//执行业务逻辑。。。
//手动制造异常
//int i = 3/0;
//肯定确认
}
}
Config
java
@Configuration
public class RabbitMQConfig {
//声明队列
@Bean(Constants.ACK_QUEUE)
public Queue ackQueue(){
return QueueBuilder.durable(Constants.ACK_QUEUE).build();
}
//声明交换机
@Bean(Constants.ACK_EXCHANGE)
public DirectExchange directExchange(){
return ExchangeBuilder.directExchange(Constants.ACK_EXCHANGE).durable(true).build();
}
//绑定队列交换机
@Bean
public Binding BindingExchange(@Qualifier(Constants.ACK_EXCHANGE) DirectExchange directExchange,
@Qualifier(Constants.ACK_QUEUE) Queue queue){
return BindingBuilder.bind(queue).to(directExchange).with("ack");
}
}
yml
XML
spring:
application:
name: rabbitmq-spring
rabbitmq:
addresses: amqp://admin:admin@60.205.230.134:5672/extension
listener:
simple:
acknowledge-mode: NONE
NONE
代码运行


可以消费正常信息也被删除,现在我们手动制造一个异常


看以看到消息依然会被删除
使用NONE策略在
- 消费者正常处理:MQ删除相应消息
- 消费者异常处理:MQ删除相应消息
AUTO
修改配置信息acknowledge-mode: AUTO
正常处理


异常处理


可以看到消息很快来到了第247条,这是因为消息处理异常后会不停的重复确认
使用NONE策略在
- 消费者正常处理:消息自动确认
- 消费者异常处理:消息会不停确认
MANUAL
MANUAL需要自己手动确认调用相应方法,通过try-catch对代码进行异常捕获,如果没有异常就执行肯定确认,否则执行否定
java
@Component
public class AckListener {
@RabbitListener(queues = Constants.ACK_QUEUE)
public void handMessage(Message message, Channel channel ) throws IOException {
try {
System.out.printf("收到的消息:%s\t deliveryTag: %d\n",new String(message.getBody(),"UTF-8")
,message.getMessageProperties().getDeliveryTag());
//执行业务逻辑。。。
//手动制造异常
//int i = 3/0;
//肯定确认
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}catch (Exception e){
//否定确认
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
}
异常处理


异常处理的现象时,不断的重复从消息队列中拿取数据,这是因为这里的否定确认,选择了处理异常消息会自动重新入队,所以就会陷入一个发送消息->消息处理异常->消息重新入队队->发送消息的循环
//这里两条是上一次演示里的那条消息没有被清除