情况1:消息没有发送到交换机,或没有发送到消息队列
方案: 消息没有发送到消息队列,我们可以在生产者端进行确认,也就是分别针对交换机和队列来确认。如果没有成功发送到消息队列服务器上,那就可以尝试重新发送。
- 开启确认模式 :根据服务器返回的
ack值判断是否重发。 - 开启回退模式:消息路由失败时,服务器退回消息,生产者可决定是否重发。
开启确认配置
yaml
logging:
level:
com.pluto.order.config.MQProducerAckConfig: info
# 配置数据库的连接信息
spring:
rabbitmq:
host:
port:
username:
password:
virtual-host: /
publisher-confirm-type: CORRELATED # 交换机的确认
publisher-returns: true # 队列的确认(队列没收到才会返回)
添加生产者配置类 MQProducerAckConfig
typescript
/**
* @author Pluto
* @date 2026/2/12 11:06
* @description: 生产者配置
*/
@Component
@Slf4j
public class MQProducerAckConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
// 注册到模板上去
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnsCallback(this);
}
// 确认回调 :交换机收没收到确定都会返回ack确认
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
log.info("消息发送到交换机成功!关联数据:" + correlationData);
} else {
log.info("消息发送到交换机失败!关联数据:" + correlationData + " 原因:" + cause);
}
}
// 返回回调 :仅仅在消息没有到队列,返回给生产者
@Override
public void returnedMessage(ReturnedMessage returned) {
log.info("消息没有到队列");
log.info("消息主体: " + new String(returned.getMessage().getBody()));
log.info("应答码: " + returned.getReplyCode());
log.info("描述:" + returned.getReplyText());
log.info("消息使用的交换器 exchange : " + returned.getExchange());
log.info("消息使用的路由键 routing : " + returned.getRoutingKey());
}
}
@PostConstruct注解是Java中的一个标准注解,它用于指定在对象创建之后立即执行的方法。当使用依赖注入(如Spring框架)或者其他方式创建对象时,@PostConstruct注解可以确保在对象完全初始化之后,执行相应的方法。
使用@PostConstruct注解的方法必须满足以下条件:
- 方法不能有任何参数。
- 方法必须是非静态的。
- 方法不能返回任何值。
当容器实例化一个带有@PostConstruct注解的Bean时,它会在调用构造函数之后,并在依赖注入完成之前调用被@PostConstruct注解标记的方法。这样,我们可以在该方法中进行一些初始化操作,比如读取配置文件、建立数据库连接等。
情况2:消息成功存入队列,但是消息队列服务器宕机,消息丢失
方案: 消息持久化到硬盘上,哪怕服务器重启也不会导致消息丢失,也就是我们需要让交换机和队列都是持久化的,这样消息就不会丢失。
这里只要在创建的时候设置持久化即可。 可以通过管理面板创建队列或者交换机的时候,设置持久化。 也可以在代码中创建交换机或者队列的时候设置持久化,以下是在修饰监听方法@RabbitListener设置的持久化。
less
public static final String EXCHANGE_DIRECT = "exchange.direct.pluto";
public static final String ROUTING_KEY = "pluto";
public static final String QUEUE_NAME = "queue.pluto";
// 修饰监听方法
@RabbitListener(
// 设置绑定关系
bindings = @QueueBinding(
// 配置队列信息:durable 为 true 表示队列持久化;autoDelete 为 false 表示关闭自动删除
value = @Queue(value = QUEUE_NAME, durable = "false", autoDelete = "true"),
// 配置交换机信息:durable 为 true 表示队列持久化;autoDelete 为 false 表示关闭自动删除
exchange = @Exchange(value = EXCHANGE_DIRECT, durable = "false", autoDelete = "true"),
// 配置路由键信息
key = {ROUTING_KEY}
))
public void processMessage(String msg, Message message, Channel channel) {
System.out.println("messageContent = " + msg);
}
durable(持久化)
- durable = true:队列/交换机的元数据会持久化到磁盘
- durable = false:队列/交换机的元数据只存在内存中
autoDelete(自动删除)
- autoDelete = true:当最后一个消费者断开连接时,队列自动删除
- autoDelete = false:队列会一直存在,即使没有消费者
情况3:消息成功存入消息队列,但是消费端出现问题
方案: 消费端异常导致消息没有成功被消费成功。默认情况下,消费端取回消息后,默认会自动返回ACK确认消息,所以还是要修改成手动确认。
yaml
spring:
rabbitmq:
host:
port:
username:
password:
virtual-host: /
listener:
simple:
acknowledge-mode: manual # 把消息确认模式改为手动确认
修改监听
java
//监听之后 手动通过唯一标识确认收到消息
@RabbitListener(queues = {"code_queue"})
public void processMessageManual(String msg, Message message, Channel channel) throws IOException {
//消息唯一标识
long deliveryTag = message.getMessageProperties().getDeliveryTag();;
log.info("messageContent = {}", msg);
try {
int i = 1 / 0;
channel.basicAck(deliveryTag, true);
} catch (Exception e) {
System.out.println("发生异常,拒绝确认消息");
// 获取信息,看当前消息是否曾经被投递过
Boolean redelivered = message.getMessageProperties().getRedelivered();
if (!redelivered) {
// 如果没有被投递过,那就重新放回队列,重新投递,再试一次
channel.basicNack(deliveryTag, false, true);
} else {
// 如果已经被投递过,且这一次仍然进入了 catch 块,那么返回拒绝且不再放回队列
channel.basicReject(deliveryTag, false);
}
}
}
这样处理完消息后,必须手动调用 basicAck 确认,服务器才会删除消息,避免丢失。