
解决producer和exchange之间丢消息的场景
- 开启发送方确认机制(publisher confirm)生产者将channel设置成confirm模式,那么在该信道上发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到exchange,rabbtimq就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一ID);
- 如果消息和队列是可持久化的,那么确认消息会在消息写入磁盘之后发出(调用了Linux内核的fsyn方法);
- RabbitTemplate通过setConfirmCallback方法设置消息发送确认回调
private RabbitTemplate.ConfirmCallback confirmCallback() {
return (data, ack, cause) -> {
if (data != null && data.getReturnedMessage() != null) {
if (ack) {
log.info("消息发送成功!messageId: {}, message: {}, ",
data.getReturnedMessage().getMessageProperties().getMessageId(),
new String(data.getReturnedMessage().getBody()));
} else {
log.warn("消息发送失败!cause:{}, data:{}", cause, data);
}
}
};
}
解决exchange和queue之间丢消息的场景
- publisher confirm机制只能确保消息正确发送至Exchange,但是消息从Exchange路由到队列还是可能会丢失的(交换器和队列没有binding)
- 针对没有匹配的队列情况,发送方需要设置mandatory参数或者配置备份交换器来提高消息传输的可靠性;
- mandatory参数:这是一个发布消息时设置的参数,具体作用是让服务器至少将消息路由到一个队列中,否则将消息返回给生产者。
- channel.addReturnListener来添加ReturnListener监听器接收返回的消息;
- RabbitTemplate可以通过setReturnCallback方法设置回调接收返回的消息;
private RabbitTemplate.ReturnCallback returnCallback() {
return (message, replyCode, replyText, exchange, routingKey) ->
log.info("消息路由失败!message:{}, replyCode:{}, replyText:{}, exchange:{}, routingKey:{}",
message, replyCode, replyText, exchange, routingKey);
}
- 备份交换器: 消息无法路由到队列时,就会把消息发往备份交换器;
- 在声明交换器的时候添加alternate-exchange参数来实现
- 备份交换器的类型一般会设置为fanout类型,因为fanout类型的交换器无需根据路由键来匹配队列,直接就会把消息转发到绑定的队列中;

解决queue和consumer之间丢消息的场景
- 消息确认机制(message acknowledgement):消费者在订阅队列时,指定autoAck参数;
- 当autoAck为true时,rabbitmq会自动把发送出去的消息置为确认,然后从内存或者磁盘中删除,而不管消费者是否真正地消费到了这些消息;
- 当autoAck为false时,rabbitmq会等待消费者回复确认信号(Basic.Ack)后才从内存(或者磁盘)中移除消息;
- 如果不手动确认,也不抛出异常,消息是不会自动重新推送的;
- 如果消费此消息的消费者已经断开连接,则rabbitmq会安排该消息重新进入队列,等待投递给下一个消费者)(这里有可能会导致消费者重复收到消息,甚至重复消费消息)
- 如果监听消息的方法抛出异常,消息会按照listener.retry的配置进行重发,但是重发次数完了之后还抛出异常的话,消息不会重发(也不会重发给其他消费者)。只有rabbitmq重启后才会重新推送。因为retry是消费端内部处理的,对于rabbitmq是不知道的;
- 当消费者发现无法正确消费消息时,可以在重试或者tryCatch的时候调用Basic.Nack将信息置入死信队列,使得后续分析程序可以通过消费这个死信队列中的内容来分析当时遇到的情况或者做一些补偿处理;
- 在声明一个普通队列时,通过设置相应参数来将一个死信交换器绑定到这个普通队列上。然后再将另一个普通队列绑定到这个死信交换器上,那后面这个普通队列就变成了死信队列了,就能够接收到在前普通队列处被消费者拒绝或者过期的消息了。队列中的消息达到最大长度也会转到死信队列中;
- 设置死信交换器 args.put(“x-dead-letter-exchange”,“exchange.dlx”);
- 设置死信路由键args.put(“x-dead-letter-routing-key”,“routingkey”); 如果没有特殊指定,则使用原队列的路由键。因此死信交换器一般是fanout类型;

解决rabbitmq服务端单点故障丢消息的场景;
- 开启交换器、队列、消息持久化机制;主要是在声明相应资源是将持久化的控制参数置为true即可开启,注意队列和消息要同时开启持久化才有用,如果队列持久化了但是消息没有持久化,那么宕机重启后消息也是会丢失的;
- rabbitmq服务作集群部署,开启镜像队列机制,那么就可以把重要队列中的消息镜像到集群中的其他broker节点上,即使集群中的一个节点失效了,队列也能够自动地切换到镜像中的另一个节点上以保证服务的可用性;