承接上文SpringBoot整合RabbitMQ
RabbitMQ的消息确认有两种。
一种是消息发送确认。这种是用来确认生产者将消息发送给交换器,交换器传递给队列的过程中,消息是否成功投递。发送确认分为两步,一是确认是否到达交换器,二是确认是否到达队列。
第二种是消费接收确认。这种是确认消费者是否成功消费了队列中的消息。
消息发送确认
通过实现ConfirmCallBack接口,消息发送到交换器Exchange后触发回调。
通过实现ReturnCallback接口,如果消息从交换器发送到对应队列失败时触发(比如根据发送消息时指定的routingKey找不到队列时会触发)
producer模块添加如下配置
spring:
rabbitmq:
publisher-confirms: true #confirm回调
publisher-returns: true #return回调
@Configuration
public class RabbitConfig {
private final Logger logger = LoggerFactory.getLogger(RabbitConfig.class);
@Bean
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
//设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
//成功发送到交换机会触发该回调
if (ack){
//表示消息发送成功,这里不做任何操作,这里只是为了写个注释(手动狗头)
}else {
//表示消息成功发送到服务器,但是没有找到交换器,这里可以记录日志,方便后续处理
logger.warn("ConfirmCallback -> 消息发布到交换器失败,错误原因为:{}", cause);
}
});
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
//表示消息发送到交换机,但是没有找到队列,这里记录日志
logger.warn("ReturnCallback -> 消息{},发送到队列失败,应答码:{},原因:{},交换器: {},路由键:{}",
message,
replyCode,
replyText,
exchange,
routingKey);
});
return rabbitTemplate;
}
}
消息接收确认
AcknowledgeMode.NONE:不确认,不会发送任何ack
AcknowledgeMode.AUTO:自动确认,就是自动发送ack,除非抛异常
AcknowledgeMode.MANUAL:手动确认,手动发送ack
ACK这里有很多坑,推荐看这篇博客RabbitMQ的ack或nack机制使用不当导致的队列堵塞或死循环问题
一般都会使用手动确认,下面在consumer模块添加配置
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual #手动应答
改造接收端代码,消费消息成功之后进行手动ack
@RabbitListener(queues = Constants.DEFAULT_DIRECT_QUEUE)
public void directConsumer(String msg, Channel channel,Message message) {
System.out.println("直连型交换机收到消息:"+msg);
//确认消息,false表示不批量处理
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
考虑消息消费失败,继续改造接收端代码
@RabbitListener(queues = Constants.DEFAULT_DIRECT_QUEUE)
public void directConsumer(String msg, Channel channel,Message message) throws IOException {
try{
//这里表示消息消费成功
System.out.println("直连型交换机收到消息:"+msg);
//确认消息,false表示不批量处理
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}catch (Exception e){
//这里表示消息消费失败,当消息消费失败时,如果没有进行处理,会导致MQ中Unacked的消息越来越多,最终占用内存越来越大
//方案一:返回NACK,并重新放入队列,这种情况可能会导致死循环
// channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);
//方案二,这里也返回ACK,记录报错日志,后续根据日志进行恢复处理
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
logger.error("消息消费失败:{}", e.getMessage());
}
}
consumer模块添加配置
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true #是否开启重试
initial-interval: 3000ms #重试时间间隔
max-attempts: 3 #重试次数
max-interval: 15000ms #重试最大时间间隔
multiplier: 2 #倍数
这里改造一下Topic主题交换机绑定的队列
@RabbitListener(queues = Constants.MAN_TOPIC_QUEUE)
public void topicManConsumer(String msg, Channel channel,Message message) throws IOException {
if ("error".equals(msg)){
//这里抛出检查其期异常不会进行重试,抛出检查期异常时重试多少次还是会抛出异常
//消息被拒绝之后不会进行重试
//抛出运行时异常会进行重试
System.out.println("消息消费失败");
throw new RuntimeException("业务出错");
}
System.out.println("主题交换机收到消息-man:"+msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
发送端发送字符串error,会发现成功触发消息重试
第一条是另一个监听队列total 收到的消息
项目代码:项目代码
相关阅读:
RabbitMQ入门
SpringBoot整合RabbitMQ
RabbitMQ的死信队列与延时队列