若想了解消费确认流程图和生产者消息确认,请到(19条消息) RabbitMQ消息可靠性(一)-- 生产者消息确认_OHJ小白的博客-CSDN博客
目录
前言
在项目中,引入了RabbitMQ这一中间件,必然也需要在业务中增加对数据安全性的一层考虑,来保证RabbitMQ消息的可靠性,否则一个个消息丢失可能导致整个业务的数据出现不一致等问题,对系统带来巨大的影响,消息的可靠性可以主要在三个方面去考虑:生产者消息确认,消费者消息确认,消息持久化,这篇文件说明消费者消息确认的。
一、消费者消息确认是什么?
在这种机制下,消费者在接收到消息后,需要向 RabbitMQ 发送确认信息,告知 RabbitMQ 已经接收到该消息,并已经处理完毕。如果 RabbitMQ 没有接收到确认信息,则会将该消息重新加入队列,等待其他消费者继续处理。
消费者消息确认机制能够保证消息不会因为消费者宕机或其他原因而丢失,从而保证了消息的可靠性和稳定性。
RabbitMQ 支持两种消费者消息确认机制:自动确认和手动确认。在自动确认模式下,消费者在接收到消息后,RabbitMQ 会自动将该消息标记为已经确认。在手动确认模式下,消费者需要向 RabbitMQ 显式地发送确认信息,才能完成消息的确认。
二、代码实现
1.修改application.yml 配置
spring:
rabbitmq:
listener:
simple:
# RabbitMQ开启手动确认
acknowledge-mode: manual
而SpringAMQP则允许配置三种确认模式:
manual:手动ack,需要在业务代码结束后,调用api发送ack。
auto:自动ack,由spring监测listener代码是否出现异常,没有异常则返回ack;抛出异常则返回nack
none:关闭ack,MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除
2.消费者确认
代码如下(示例):
/**
* Created by YHJ on 2022/08/16 23:12
*/
@Component
public class Consumer {
// 准备日志打印,使用slf4j日志打印
private static final Logger LOGGER = LoggerFactory.getLogger(Consumer.class);
@Autowired
private RedisTemplate redisTemplate;
@RabbitListener(queues = "YHJ.queues") // 改为定义的常量
public void handler(Message message, Channel channel) {
Employee employee = (Employee) message.getPayload();
MessageHeaders headers = message.getHeaders();
// 消息序号
long tag = (long) headers.get(AmqpHeaders.DELIVERY_TAG);
// 取出消息id
String msgId = (String) headers.get("spring_returned_message_correlation");
HashOperations hashOperations = redisTemplate.opsForHash();
try {
//TODO 消息消费幂等性,判断这条信息是否被消费过的了,以免多条一样的消息被消费而造成混乱
if (hashOperations.entries("emp_log").containsKey(msgId)) { //判断redis是否存在此条信息的id
LOGGER.error("消息已经被消费============>{}", msgId);
/**
* TODO 手动确认消息
* tag:消息序号
* multiple:消息的标识,是否确认多条,false只确认当前一个消息收到,true确认所有consumer获得的消息(成功消费,消息从队列中删除
*/
channel.basicAck(tag, false);
return;
}
//TODO 消费者消费信息(自己的业务逻辑).......
// 将消息id存入redis
hashOperations.put("emp_log", msgId, "OK");
// 手动确认消息,第二个参数是:是否要确认多条信息,这里是一条条信息来的,所以不需要开启
channel.basicAck(tag, false);
} catch (Exception e) {
/**
* TODO 消费者消费消息异常,手动否认信息,将消息退回到队列中
* tag:消息序号
* multiple:消息的标识,是否确认多条,false只确认当前一个消息收到,true确认所有consumer获得的消息(成功消费,消息从队列中删除
* requeue:是否要退回到队列
*/
try {
channel.basicNack(tag, false, true);
} catch (IOException ex) {
ex.printStackTrace();
}
LOGGER.error("消费失败=======>{}", e.getMessage());
}
}
}
要注意这里的消息序号和消息id是不同的
还需要注意的是,在手动确认模式下,如果消费者在处理消息时发生了异常或错误,也需要确保将该消息重新加入队列,否则该消息将被认为已经成功处理并确认。因此,在编写消费者代码时,需要谨慎处理异常情况,避免因为异常而导致消息丢失或重复处理等问题。需要注意的是,在手动确认模式下,如果消费者在处理消息时发生了异常或错误,也需要确保将该消息重新加入队列,否则该消息将被认为已经成功处理并确认。因此,在编写消费者代码时,需要谨慎处理异常情况,避免因为异常而导致消息丢失或重复处理等问题。
总结
本文仅仅简单介绍了消费者消息确认,MQ 的消费者消息确认指的是 RabbitMQ 作为消息中间件时,为防止消息丢失而增加的一种消息确认机制。